Port Round 1 to Python (OrderDomain): 7 tests green, START state preserved
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from order_domain.order import Order, OrderLine
|
||||
|
||||
__all__ = ["Order", "OrderLine"]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class OrderLine:
|
||||
def __init__(self, unit_price: Decimal, quantity: int):
|
||||
self.unit_price = unit_price
|
||||
self.quantity = quantity
|
||||
self.has_discount = False
|
||||
|
||||
def apply_discount(self) -> None:
|
||||
self.has_discount = True
|
||||
|
||||
@property
|
||||
def value(self) -> Decimal:
|
||||
if self.has_discount:
|
||||
return self.unit_price * self.quantity * Decimal("0.8")
|
||||
return self.unit_price * self.quantity
|
||||
|
||||
|
||||
class Order:
|
||||
def __init__(self):
|
||||
self.lines: list[OrderLine] = []
|
||||
self.is_completed = False
|
||||
|
||||
@property
|
||||
def total(self) -> Decimal:
|
||||
return sum(line.value for line in self.lines)
|
||||
|
||||
def add_line(self, unit_price: Decimal, quantity: int) -> None:
|
||||
self.lines.append(OrderLine(unit_price, quantity))
|
||||
|
||||
def apply_discount(self, line_index: int) -> None:
|
||||
self.lines[line_index].apply_discount()
|
||||
|
||||
def complete(self) -> None:
|
||||
self.is_completed = True
|
||||
|
||||
def is_fully_paid(self, expected_total: Decimal) -> bool:
|
||||
return self.is_completed and self.total == expected_total
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Custom matcher that bundles multiple order assertions into one.
|
||||
|
||||
Participants: you do not need to understand how this works —
|
||||
just recognize the pattern and use it as an example for the agent.
|
||||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from order_domain import Order
|
||||
|
||||
|
||||
def assert_order_state(
|
||||
order: Order, *, total: Decimal, line_values: list[Decimal]
|
||||
) -> None:
|
||||
actual_total = order.total
|
||||
actual_line_values = [line.value for line in order.lines]
|
||||
|
||||
details: list[str] = []
|
||||
if actual_total != total:
|
||||
details.append(f"Total: expected {total}, but was {actual_total}")
|
||||
if actual_line_values != line_values:
|
||||
details.append(
|
||||
f"Line values: expected [{', '.join(str(v) for v in line_values)}], "
|
||||
f"but was [{', '.join(str(v) for v in actual_line_values)}]"
|
||||
)
|
||||
|
||||
if details:
|
||||
header = (
|
||||
f"Expected order with total {total} and line values "
|
||||
f"[{', '.join(str(v) for v in line_values)}] but was:"
|
||||
)
|
||||
raise AssertionError("\n".join([header, *details]))
|
||||
@@ -0,0 +1,11 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from order_domain import Order
|
||||
|
||||
|
||||
def test_completed_order_total_is_correct():
|
||||
order = Order()
|
||||
order.add_line(Decimal("100"), 1)
|
||||
order.complete()
|
||||
|
||||
assert order.is_fully_paid(Decimal("100"))
|
||||
@@ -0,0 +1,77 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from order_domain import Order
|
||||
from order_state import assert_order_state
|
||||
|
||||
|
||||
# This test was refactored - notice the custom matcher.
|
||||
# The other tests still need the same treatment.
|
||||
def test_discount_on_first_line_total_and_line_values():
|
||||
order = Order()
|
||||
order.add_line(Decimal("100"), 2) # line 0: 200 -> 160 with discount
|
||||
order.add_line(Decimal("50"), 1) # line 1: 50
|
||||
|
||||
order.apply_discount(0)
|
||||
|
||||
assert_order_state(
|
||||
order, total=Decimal("210"), line_values=[Decimal("160"), Decimal("50")]
|
||||
)
|
||||
|
||||
|
||||
def test_discount_on_second_line_total_and_line_values():
|
||||
order = Order()
|
||||
order.add_line(Decimal("100"), 2) # line 0: 200
|
||||
order.add_line(Decimal("50"), 1) # line 1: 50 -> 40 with discount
|
||||
|
||||
order.apply_discount(1)
|
||||
|
||||
assert order.total == Decimal("240")
|
||||
assert order.lines[0].value == Decimal("200")
|
||||
assert order.lines[1].value == Decimal("40")
|
||||
|
||||
|
||||
def test_discount_on_both_lines_total_and_line_values():
|
||||
order = Order()
|
||||
order.add_line(Decimal("100"), 2) # line 0: 200 -> 160
|
||||
order.add_line(Decimal("50"), 1) # line 1: 50 -> 40
|
||||
|
||||
order.apply_discount(0)
|
||||
order.apply_discount(1)
|
||||
|
||||
assert order.total == Decimal("200")
|
||||
assert order.lines[0].value == Decimal("160")
|
||||
assert order.lines[1].value == Decimal("40")
|
||||
|
||||
|
||||
def test_no_discount_total_and_line_values():
|
||||
order = Order()
|
||||
order.add_line(Decimal("100"), 2) # line 0: 200
|
||||
order.add_line(Decimal("50"), 1) # line 1: 50
|
||||
|
||||
assert order.total == Decimal("250")
|
||||
assert order.lines[0].value == Decimal("200")
|
||||
assert order.lines[1].value == Decimal("50")
|
||||
|
||||
|
||||
def test_single_line_with_discount_total_and_value():
|
||||
order = Order()
|
||||
order.add_line(Decimal("75"), 4) # line 0: 300 -> 240
|
||||
|
||||
order.apply_discount(0)
|
||||
|
||||
assert order.total == Decimal("240")
|
||||
assert order.lines[0].value == Decimal("240")
|
||||
|
||||
|
||||
def test_three_lines_one_discount_total_and_line_values():
|
||||
order = Order()
|
||||
order.add_line(Decimal("20"), 1) # line 0: 20
|
||||
order.add_line(Decimal("30"), 2) # line 1: 60 -> 48
|
||||
order.add_line(Decimal("10"), 3) # line 2: 30
|
||||
|
||||
order.apply_discount(1)
|
||||
|
||||
assert order.total == Decimal("98")
|
||||
assert order.lines[0].value == Decimal("20")
|
||||
assert order.lines[1].value == Decimal("48")
|
||||
assert order.lines[2].value == Decimal("30")
|
||||
Reference in New Issue
Block a user