Skip to content

Commit

Permalink
Introduce Patron command handler
Browse files Browse the repository at this point in the history
This commit includes the PlaceHold command along with its corresponding command handler
and method.
  • Loading branch information
subhashb committed Aug 13, 2024
1 parent 6c03f2c commit 3d88a88
Show file tree
Hide file tree
Showing 40 changed files with 113 additions and 20 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"module": "pytest",
"justMyCode": false,
"args": [
"tests/lending/bdd/checkouts/test_checkouts.py::test_patron_checks_out_a_book_on_hold",
"tests/lending/model/bdd/holds/test_holds.py::test_regular_patron_tries_to_place_an_openended_hold",
]
},
]
Expand Down
2 changes: 2 additions & 0 deletions src/lending/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)

from lending.app.dailysheet import DailySheet # isort:skip
from lending.app.patron.hold import PlaceHold # isort:skip


__all__ = [
Expand All @@ -33,6 +34,7 @@
"BookStatus",
"BookType",
"place_hold",
"PlaceHold",
"DailySheetService",
"checkout",
"DailySheet",
Expand Down
File renamed without changes.
24 changes: 24 additions & 0 deletions src/lending/app/patron/hold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from protean import current_domain, handle
from protean.fields import Identifier, String

from lending import Book, Patron, place_hold
from lending.domain import lending


@lending.command(part_of="Patron")
class PlaceHold:
patron_id = Identifier(required=True)
book_id = Identifier(required=True)
branch_id = Identifier(required=True)
hold_type = String(required=True)


@lending.command_handler(part_of="Patron")
class HoldCommandHandler:
@handle(PlaceHold)
def handle_PlaceHold(self, command: PlaceHold) -> None:
patron = current_domain.repository_for(Patron).get(command.patron_id)
book = current_domain.repository_for(Book).get(command.book_id)

place_hold(patron, book, command.branch_id, command.hold_type)()
current_domain.repository_for(Patron).add(patron)
6 changes: 3 additions & 3 deletions src/lending/model/holding_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
@lending.domain_service(part_of=[Patron, Book])
class place_hold:
def __init__(
self, patron: Patron, book: Book, branch_id: Identifier, hold_type: HoldType
self, patron: Patron, book: Book, branch_id: Identifier, hold_type: str
):
self.patron = patron
self.book = book
Expand Down Expand Up @@ -49,7 +49,7 @@ def book_already_on_hold_cannot_be_placed_on_hold(self):
def regular_patrons_cannot_place_open_ended_holds(self):
if (
self.patron.patron_type == PatronType.REGULAR.value
and self.hold_type == HoldType.OPEN_ENDED
and self.hold_type == HoldType.OPEN_ENDED.value
):
raise ValidationError(
{"hold_type": ["Regular patrons cannot place open-ended holds"]}
Expand Down Expand Up @@ -97,7 +97,7 @@ def __call__(self):
hold = Hold(
book_id=self.book.id,
branch_id=self.branch_id,
hold_type=self.hold_type.value,
hold_type=self.hold_type,
status=HoldStatus.ACTIVE.value,
requested_at=datetime.now(),
expires_on=expires_on,
Expand Down
4 changes: 3 additions & 1 deletion src/lending/model/patron/hold.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ def expire(self):
)

def cancel(self):
if self.status == HoldStatus.EXPIRED.value or self.expires_on < date.today():
if self.status == HoldStatus.EXPIRED.value or (
self.expires_on is not None and self.expires_on < date.today()
):
raise ValidationError({"expired_hold": ["Cannot cancel expired holds"]})

if self.status == HoldStatus.CHECKED_OUT.value:
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Feature: Place a Hold on a Book

Scenario: Regular patron places a hold on an available circulating book
Given a circulating book is available
And a regular patron is logged in
When the patron places a hold on the book
Then the hold is successfully placed
And the book is marked as held
File renamed without changes.
52 changes: 52 additions & 0 deletions tests/lending/app/bdd/hold_handlers/step_defs/hold_steps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import pytest
from protean import current_domain, g
from pytest_bdd import given, then, when

from lending import Book, BookStatus, HoldType, PlaceHold


@pytest.fixture(autouse=True)
def reset_globals():
yield

if hasattr(g, "current_user"):
delattr(g, "current_user")
if hasattr(g, "current_book"):
delattr(g, "current_book")
if hasattr(g, "current_exception"):
delattr(g, "current_exception")


@given("a circulating book is available")
def a_circulating_book_is_available(circulating_book):
g.current_book = circulating_book


@given("a regular patron is logged in")
def a_regular_patron_is_logged_in(regular_patron):
g.current_user = regular_patron


@when("the patron places a hold on the book")
def the_patron_places_a_hold_on_the_book():
command = PlaceHold(
patron_id=g.current_user.id,
book_id=g.current_book.id,
branch_id="1",
hold_type=HoldType.CLOSED_ENDED.value,
)
current_domain.process(command)


@then("the hold is successfully placed")
def the_hold_is_successfully_placed():
message = current_domain.event_store.store.read_last_message(
f"library::patron-{g.current_user.id}"
)
assert message.metadata.type == "Library.HoldPlaced.v1"


@then("the book is marked as held")
def the_book_is_marked_as_held():
book = current_domain.repository_for(Book).get(g.current_book.id)
assert book.status == BookStatus.ON_HOLD.value
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def regular_patron(regular_patron):

@given("the patron has a hold on the book")
def patron_with_active_hold(patron, book):
place_hold(g.current_user, book, "1", HoldType.CLOSED_ENDED)()
place_hold(g.current_user, book, "1", HoldType.CLOSED_ENDED.value)()


@given("a patron has checked out a book")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def patron_with_active_hold(patron, book):

with UnitOfWork():
refreshed_patron = current_domain.repository_for(Patron).get(patron.id)
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)


Expand Down Expand Up @@ -95,7 +95,7 @@ def generated_daily_sheet_for_expiring_holds(patron, book):

# Place Hold
refreshed_patron = current_domain.repository_for(Patron).get(patron.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)

# Expire Hold
Expand Down Expand Up @@ -152,7 +152,7 @@ def generated_daily_sheet_for_overdue_checkouts(patron, book):
def place_hold_on_book():
try:
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)
except ValidationError as exc:
g.current_exception = exc
Expand Down Expand Up @@ -214,7 +214,7 @@ def generate_daily_sheet(patron, book):

# Place Hold
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)

# Expire Hold
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def more_than_two_overdue_checkouts(overdue_checkouts_patron):
@given("a closed-ended hold is placed")
def closed_ended_hold_placed():
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)


Expand All @@ -86,7 +86,7 @@ def patron_with_fewer_than_five_holds():
def patron_with_exactly_five_holds(five_books):
for i in range(5):
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, five_books[i], "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, five_books[i], "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)

refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
Expand All @@ -99,7 +99,7 @@ def patron_with_active_hold(patron, book):
g.current_book = book

refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)


Expand All @@ -109,7 +109,7 @@ def patron_with_expired_hold(patron, book):
g.current_book = book

refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)

refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
Expand All @@ -123,7 +123,7 @@ def patron_with_checked_out_hold(patron, book):
g.current_book = book

refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)

refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
Expand All @@ -138,7 +138,7 @@ def patron_with_checked_out_hold(patron, book):
def place_hold_on_book():
try:
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)
except ValidationError as exc:
g.current_exception = exc
Expand All @@ -149,7 +149,7 @@ def place_hold_on_book():
def place_open_ended_hold():
try:
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.OPEN_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.OPEN_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)
except ValidationError as exc:
g.current_exception = exc
Expand All @@ -159,7 +159,7 @@ def place_open_ended_hold():
def closed_ended_hold():
try:
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)
except ValidationError as exc:
g.current_exception = exc
Expand All @@ -176,12 +176,12 @@ def check_expiring_holds():
def place_more_than_five_holds(five_books):
for i in range(5):
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, five_books[i], "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, five_books[i], "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)

# Place one more hold
refreshed_patron = current_domain.repository_for(Patron).get(g.current_user.id)
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED)()
place_hold(refreshed_patron, g.current_book, "1", HoldType.CLOSED_ENDED.value)()
current_domain.repository_for(Patron).add(refreshed_patron)


Expand Down
5 changes: 5 additions & 0 deletions tests/lending/model/bdd/holds/test_holds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from pytest_bdd import scenarios

from .step_defs.hold_steps import * # noqa: F403

scenarios("./features")
Empty file.
Empty file.

0 comments on commit 3d88a88

Please sign in to comment.