Available stock counts drift from reality
Done- Plan (done)
- Code (done)
- Test (done)
- Review (done)
- Finish (done)
Cost
$0.0053
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
11
Prompt read from cache
76%
Usage by agent
- Cache read
- Cache write
- Input
- Output
View as table
| Agent | Cost | Cache read | Cache write | Input | Output |
|---|---|---|---|---|---|
| planner | $0.0013 | 9,449 | 1,854 | 10 | 1,979 |
| coder | $0.0021 | 15,908 | 4,710 | 10 | 2,708 |
| tester | $0.0019 | 14,947 | 6,463 | 8 | 1,862 |
Timeline
-
planner iteration 1 Finished 4 calls · 13.3K tokens · $0.0013 · 13.3s
Loading…
-
coder iteration 1 Finished 4 calls · 23.3K tokens · $0.0021 · 12.8s
Loading…
-
tester iteration 1 Finished 3 calls · 23.3K tokens · $0.0019 · 10.9s
Loading…
-
verify iteration 1 Passed 0.6s
............. [100%] 13 passed in 0.03s
Final diff
diff --git a/orders.py b/orders.py index 3c6e298..6a5f045 100644 --- a/orders.py +++ b/orders.py @@ -1,4 +1,4 @@ -from stock import Stock +from stock import OutOfStock, Stock class OrderError(Exception): @@ -11,19 +11,39 @@ class Orders: self._orders: dict[int, dict] = {} self._next_id = 1 + def _get(self, order_id: int) -> dict: + try: + return self._orders[order_id] + except KeyError: + raise OrderError(f"unknown order id {order_id}") from None + def place(self, lines: dict[str, int]) -> int: - for sku, qty in lines.items(): - self.stock.reserve(sku, qty) + reserved: list[tuple[str, int]] = [] + try: + for sku, qty in lines.items(): + self.stock.reserve(sku, qty) + reserved.append((sku, qty)) + except OutOfStock: + for sku, qty in reserved: + self.stock.release(sku, qty) + raise order_id = self._next_id self._next_id += 1 self._orders[order_id] = {"lines": dict(lines), "status": "open"} return order_id def ship(self, order_id: int) -> None: - self._orders[order_id]["status"] = "shipped" + order = self._get(order_id) + if order["status"] == "cancelled": + raise OrderError(f"order {order_id} is cancelled and cannot be shipped") + order["status"] = "shipped" def cancel(self, order_id: int) -> None: - order = self._orders[order_id] + order = self._get(order_id) + if order["status"] == "cancelled": + return + if order["status"] == "shipped": + raise OrderError(f"order {order_id} has shipped and cannot be cancelled") for sku, qty in order["lines"].items(): self.stock.release(sku, qty) order["status"] = "cancelled" diff --git a/test_orders.py b/test_orders.py index 149497c..51a8275 100644 --- a/test_orders.py +++ b/test_orders.py @@ -1,5 +1,7 @@ -from orders import Orders -from stock import Stock +import pytest + +from orders import OrderError, Orders +from stock import OutOfStock, Stock def test_place_reserves(): @@ -13,3 +15,109 @@ def test_cancel_releases(): orders = Orders(stock) orders.cancel(orders.place({"a": 2})) assert stock.available("a") == 5 + + +def test_double_cancel_restores_stock_once(): + stock = Stock({"a": 5}) + orders = Orders(stock) + order_id = orders.place({"a": 2}) + orders.cancel(order_id) + orders.cancel(order_id) + assert stock.available("a") == 5 + + +def test_failed_multiline_place_leaves_stock_unchanged(): + stock = Stock({"a": 5, "b": 1}) + orders = Orders(stock) + with pytest.raises(OutOfStock): + orders.place({"a": 2, "b": 3}) + assert stock.available("a") == 5 + assert stock.available("b") == 1 + assert orders._orders == {} + + +def test_cancel_shipped_order_raises_and_changes_nothing(): + stock = Stock({"a": 5}) + orders = Orders(stock) + order_id = orders.place({"a": 2}) + orders.ship(order_id) + with pytest.raises(OrderError): + orders.cancel(order_id) + assert stock.available("a") == 3 + assert orders._orders[order_id]["status"] == "shipped" + + +def test_ship_cancelled_order_raises_and_stays_cancelled(): + stock = Stock({"a": 5}) + orders = Orders(stock) + order_id = orders.place({"a": 2}) + orders.cancel(order_id) + with pytest.raises(OrderError): + orders.ship(order_id) + assert orders._orders[order_id]["status"] == "cancelled" + + +def test_unknown_order_id_raises_order_error(): + orders = Orders(Stock({"a": 5})) + with pytest.raises(OrderError): + orders.ship(999) + with pytest.raises(OrderError): + orders.cancel(999) + + +def test_failed_place_with_first_line_out_of_stock_leaves_stock_unchanged(): + stock = Stock({"a": 1, "b": 5}) + orders = Orders(stock) + with pytest.raises(OutOfStock): + orders.place({"a": 2, "b": 3}) + assert stock.available("a") == 1 + assert stock.available("b") == 5 + assert orders._orders == {} + + +def test_failed_place_with_unknown_sku_leaves_stock_unchanged(): + stock = Stock({"a": 5}) + orders = Orders(stock) + with pytest.raises(OutOfStock): + orders.place({"a": 2, "missing": 1}) + assert stock.available("a") == 5 + assert stock.available("missing") == 0 + + +def test_failed_place_does_not_consume_order_id(): + stock = Stock({"a": 5, "b": 1}) + orders = Orders(stock) + with pytest.raises(OutOfStock): + orders.place({"a": 1, "b": 2}) + assert orders.place({"a": 1}) == 1 + + +def test_ship_unknown_id_changes_nothing(): + stock = Stock({"a": 5}) + orders = Orders(stock) + order_id = orders.place({"a": 2}) + with pytest.raises(OrderError): + orders.ship(999) + assert orders._orders[order_id]["status"] == "open" + assert stock.available("a") == 3 + + +def test_cancel_unknown_id_changes_nothing(): + stock = Stock({"a": 5}) + orders = Orders(stock) + order_id = orders.place({"a": 2}) + with pytest.raises(OrderError): + orders.cancel(999) + assert orders._orders[order_id]["status"] == "open" + assert stock.available("a") == 3 + + +def test_cancel_after_double_cancel_stays_cancelled_and_no_raise(): + stock = Stock({"a": 5}) + orders = Orders(stock) + order_id = orders.place({"a": 2}) + orders.cancel(order_id) + orders.cancel(order_id) + orders.cancel(order_id) + assert stock.available("a") == 5 + assert orders._orders[order_id]["status"] == "cancelled"