ooad1-12 Exercise 2: Requirement R1, 'See the Sessions and the Seats Left' ============================================================================= ex2/browse.py: """Exercise 2: requirement R1 ('see the upcoming sessions and how many seats are left'), added as a read-only query beside the desk.""" from dataclasses import dataclass from datetime import date from workshops.application import SessionStore @dataclass(frozen=True) class SessionRow: session_id: str workshop: str starts_on: date seats_left: int fee_pence: int class Browser: def __init__(self, store: SessionStore): self.store = store def upcoming(self, today: date) -> list[SessionRow]: rows = [SessionRow(s.id, s.workshop, s.starts_on, s.capacity - s.seats_in_use(), s.fee_pence) for s in self.store.all_sessions() if not s.cancelled and s.starts_on > today] return sorted(rows, key=lambda r: (r.starts_on, r.session_id)) ex2/test_browse.py: import sys import unittest from datetime import date sys.path.insert(0, "ex2") from browse import Browser from workshops.application import BookingDesk from workshops.domain import Session from workshops.infrastructure import FakePayments, InMemoryStore TODAY = date(2026, 11, 1) class Tests(unittest.TestCase): def setUp(self): self.sessions = [Session("S2", "Coding", date(2026, 11, 20), 5, 4000), Session("S1", "Pottery", date(2026, 12, 1), 2, 2500), Session("S0", "Old", date(2026, 10, 1), 5, 1000), Session("S3", "Cancelled one", date(2026, 12, 5), 5, 1000)] self.store = InMemoryStore(self.sessions) self.desk = BookingDesk(self.store, FakePayments()) self.sessions[3].cancelled = True def test_only_upcoming_uncancelled_sessions_in_date_order(self): self.assertEqual([r.session_id for r in Browser(self.store).upcoming(TODAY)], ["S2", "S1"]) def test_seats_left_goes_down_as_people_book(self): self.desk.book("ann", "S1", TODAY) rows = {r.session_id: r for r in Browser(self.store).upcoming(TODAY)} self.assertEqual((rows["S1"].seats_left, rows["S2"].seats_left), (1, 5)) def test_a_full_session_shows_zero_seats_left_not_a_negative_number(self): self.desk.book("ann", "S1", TODAY) self.desk.book("bo", "S1", TODAY) self.desk.book("cy", "S1", TODAY) # waitlisted: holds no seat self.assertEqual({r.session_id: r.seats_left for r in Browser(self.store).upcoming(TODAY)}["S1"], 0) if __name__ == "__main__": unittest.main() Run from the chapter folder with: py -m unittest ex2.test_browse Output: Ran 3 tests in 0.000s OK THE DESIGN CHOICES - A query is separate from the desk because it changes nothing: it has no payment, no events, and no state machine. Putting it on BookingDesk would grow the class that exists to coordinate changes. - It depends only on the SessionStore interface, so it works with any provider of it. - seats_left is capacity minus the seats in use (Requested, Offered and Confirmed bookings hold seats, Waitlisted ones do not), so a full session shows 0 and a waitlist never makes it negative. - Upcoming means 'starts after today', matching the rule that a session that has started cannot be booked, and cancelled sessions are not shown. WHY THIS WORKS AS AN ANSWER --------------------------- The requirement was missing from the design (a gap in my first reading, which went straight to booking), and adding it needed no change to the existing code: one new class that depends on an existing interface. The class diagram would gain Browser and SessionRow, and the design check could be extended to cover them. The tests pin the three decisions above.