Fix egg sale form: remove duplicate route, change price to euros
All checks were successful
Deploy / deploy (push) Successful in 2m50s

The egg sale form had two issues:
- Duplicate POST /actions/product-sold route in products.py was
  overwriting the eggs.py handler, causing incomplete page responses
  (no tabs, no recent sales list)
- Price input used cents while feed purchase uses euros, inconsistent UX

Changes:
- Remove duplicate handler from products.py (keep only redirect)
- Change sell form price input from cents to euros (consistent with feed)
- Parse euros in handler, convert to cents for storage
- Add TestEggSale class with 4 tests for the fixed behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-22 07:35:02 +00:00
parent 51e502ed10
commit ffef49b931
5 changed files with 109 additions and 201 deletions

View File

@@ -365,3 +365,66 @@ class TestEggCollectionAnimalFiltering:
"Juvenile should NOT be associated with egg collection"
)
assert len(associated_ids) == 1, "Only adult females should be associated"
class TestEggSale:
"""Tests for POST /actions/product-sold from eggs page."""
def test_sell_form_accepts_euros(self, client, seeded_db):
"""Price input should accept decimal euros like feed purchase."""
resp = client.post(
"/actions/product-sold",
data={
"product_code": "egg.duck",
"quantity": "10",
"total_price_euros": "12.50", # Euros, not cents
"nonce": "test-nonce-sell-euros-1",
},
)
assert resp.status_code == 200
# Event should store 1250 cents
import json
event_row = seeded_db.execute(
"SELECT entity_refs FROM events WHERE type = 'ProductSold' ORDER BY id DESC LIMIT 1"
).fetchone()
entity_refs = json.loads(event_row[0])
assert entity_refs["total_price_cents"] == 1250
def test_sell_response_includes_tabs(self, client, seeded_db):
"""After recording sale, response should include full page with tabs."""
resp = client.post(
"/actions/product-sold",
data={
"product_code": "egg.duck",
"quantity": "10",
"total_price_euros": "15.00",
"nonce": "test-nonce-sell-tabs-1",
},
)
assert resp.status_code == 200
# Should have both tabs (proving it's the full eggs page)
assert "Harvest" in resp.text
assert "Sell" in resp.text
def test_sell_response_includes_recent_sales(self, client, seeded_db):
"""After recording sale, response should include recent sales section."""
resp = client.post(
"/actions/product-sold",
data={
"product_code": "egg.duck",
"quantity": "10",
"total_price_euros": "15.00",
"nonce": "test-nonce-sell-recent-1",
},
)
assert resp.status_code == 200
assert "Recent Sales" in resp.text
def test_sell_form_has_euros_field(self, client):
"""Sell form should have total_price_euros field, not total_price_cents."""
resp = client.get("/?tab=sell")
assert resp.status_code == 200
assert 'name="total_price_euros"' in resp.text
assert "Total Price" in resp.text

View File

@@ -59,10 +59,10 @@ class TestProductSoldFormRendering:
assert 'name="quantity"' in resp.text or 'id="quantity"' in resp.text
def test_sell_form_has_total_price_field(self, client):
"""Form has total_price_cents input field."""
"""Form has total_price_euros input field."""
resp = client.get("/sell")
assert resp.status_code == 200
assert 'name="total_price_cents"' in resp.text or 'id="total_price_cents"' in resp.text
assert 'name="total_price_euros"' in resp.text or 'id="total_price_euros"' in resp.text
def test_sell_form_has_buyer_field(self, client):
"""Form has optional buyer input field."""
@@ -89,7 +89,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "30",
"total_price_cents": "1500",
"total_price_euros": "15.00",
"buyer": "Local Market",
"notes": "Weekly sale",
"nonce": "test-nonce-sold-1",
@@ -113,7 +113,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "30",
"total_price_cents": "1500",
"total_price_euros": "15.00",
"nonce": "test-nonce-sold-2",
},
)
@@ -136,7 +136,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "3",
"total_price_cents": "1000",
"total_price_euros": "10.00",
"nonce": "test-nonce-sold-3",
},
)
@@ -158,7 +158,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "0",
"total_price_cents": "1000",
"total_price_euros": "10.00",
"nonce": "test-nonce-sold-4",
},
)
@@ -172,7 +172,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "-1",
"total_price_cents": "1000",
"total_price_euros": "10.00",
"nonce": "test-nonce-sold-5",
},
)
@@ -186,7 +186,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "10",
"total_price_cents": "-100",
"total_price_euros": "-1.00",
"nonce": "test-nonce-sold-6",
},
)
@@ -199,7 +199,7 @@ class TestProductSold:
"/actions/product-sold",
data={
"quantity": "10",
"total_price_cents": "1000",
"total_price_euros": "10.00",
"nonce": "test-nonce-sold-7",
},
)
@@ -213,30 +213,29 @@ class TestProductSold:
data={
"product_code": "invalid.product",
"quantity": "10",
"total_price_cents": "1000",
"total_price_euros": "10.00",
"nonce": "test-nonce-sold-8",
},
)
assert resp.status_code == 422
def test_product_sold_success_shows_toast(self, client):
"""Successful sale returns response with toast trigger."""
def test_product_sold_success_returns_full_page(self, client):
"""Successful sale returns full eggs page with tabs."""
resp = client.post(
"/actions/product-sold",
data={
"product_code": "egg.duck",
"quantity": "12",
"total_price_cents": "600",
"total_price_euros": "6.00",
"nonce": "test-nonce-sold-9",
},
)
assert resp.status_code == 200
# Check for HX-Trigger header with showToast
hx_trigger = resp.headers.get("HX-Trigger")
assert hx_trigger is not None
assert "showToast" in hx_trigger
# Should return full eggs page with tabs (toast via session)
assert "Harvest" in resp.text
assert "Sell" in resp.text
def test_product_sold_optional_buyer(self, client, seeded_db):
"""Buyer field is optional."""
@@ -245,7 +244,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "10",
"total_price_cents": "500",
"total_price_euros": "5.00",
"nonce": "test-nonce-sold-10",
},
)
@@ -265,7 +264,7 @@ class TestProductSold:
data={
"product_code": "egg.duck",
"quantity": "10",
"total_price_cents": "500",
"total_price_euros": "5.00",
"buyer": "Test Buyer",
"nonce": "test-nonce-sold-11",
},