smart-w / scripts /test_bug_report_fixes.py
initiumzim
Fix the June field bug report; Gemini-native unified audio (ADRs 0016, 0017)
3ea2835
Raw
History Blame Contribute Delete
10.4 kB
# Regression tests for the field bug-report fixes of 2026-06-03 (see ADR 0016 and
# ADR 0017). Run with: python scripts/test_bug_report_fixes.py
# Mocks Firebase/Gemini so main.py imports cleanly; only pure logic is exercised.
import os, sys, json
os.environ.setdefault("GEMINI_API_KEY", "fake-key-for-tests")
os.environ.setdefault("FIREBASE", "{}")
os.environ.setdefault("aai_key", "fake")
os.environ.setdefault("whatsapp_token", "fake")
os.environ.setdefault("phone_number_id", "0000")
os.environ.setdefault("WHATSAPP_PROXY_URL", "")
os.environ.setdefault("VERIFY_TOKEN", "fake")
from unittest import mock
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import firebase_admin
from firebase_admin import credentials, firestore
_fake_db = mock.MagicMock()
with mock.patch.object(credentials, "Certificate", return_value=mock.MagicMock()), \
mock.patch.object(firebase_admin, "initialize_app", return_value=mock.MagicMock()), \
mock.patch.object(firestore, "client", return_value=_fake_db):
import main
import utility
PASS = FAIL = 0
def check(desc, got, want):
global PASS, FAIL
ok = got == want
PASS += ok; FAIL += (not ok)
print(("PASS" if ok else "FAIL") + f" {desc}\n got={got!r} want={want!r}" if not ok else f"PASS {desc}")
def check_true(desc, got): check(desc, bool(got), True)
def check_false(desc, got): check(desc, bool(got), False)
print("== Bug 2: price-format equivalence (_parse_price_reply) ==")
check("carrot: USD0.15", main._parse_price_reply("carrot: USD0.15"), {"carrot": 0.15})
check("carrot: 0.15 dollars", main._parse_price_reply("carrot: 0.15 dollars"), {"carrot": 0.15})
check("carrot: 0.15 USD", main._parse_price_reply("carrot: 0.15 USD"), {"carrot": 0.15})
check("carrot: 15 cents", main._parse_price_reply("Carrot: 15 cents"), {"carrot": 0.15})
check("carrot: 50c", main._parse_price_reply("carrot: 50c"), {"carrot": 0.5})
check("peanut butter - $3", main._parse_price_reply("peanut butter - $3"), {"peanut butter": 3.0})
check("multiline", main._parse_price_reply("jam: USD2\nbread 1.20 each"), {"jam": 2.0, "bread": 1.2})
check("skip", main._parse_price_reply("skip"), {})
check("CRUD not price", main._parse_price_reply("update transaction abc123 to USD12"), None)
check("question not price", main._parse_price_reply("what is on sale?"), None)
print("== Bug 2: bare money reply ==")
check("USD0.50", main._parse_bare_money_reply("USD0.50"), 0.5)
check("0.50 dollars", main._parse_bare_money_reply("0.50 dollars"), 0.5)
check("50c", main._parse_bare_money_reply("50c"), 0.5)
check("0.50 each", main._parse_bare_money_reply("0.50 each"), 0.5)
check("plain 2", main._parse_bare_money_reply("2"), 2.0)
check("not money", main._parse_bare_money_reply("2 jam"), None)
check("not money txt", main._parse_bare_money_reply("hello"), None)
print("== Bug 1: move-item extraction ==")
check("move dovi", main._extract_move_item("move dovi"), "dovi")
check("put dovi on sale", main._extract_move_item("put dovi on sale"), "dovi")
check("discount tomatoes", main._extract_move_item("discount tomatoes"), "tomatoes")
check("question guarded", main._extract_move_item("what is on sale?"), None)
print("== Bugs 4+5: debt query detection ==")
check_true(" who do I owe money?", utility.is_debt_query("Who do I owe money?"))
check_true(" who do I owe?", utility.is_debt_query("Who do I owe?"))
check_true(" who owes me money?", utility.is_debt_query("Who owes me money?"))
check_true(" show my payables", utility.is_debt_query("show my payables"))
check_true(" my debtors", utility.is_debt_query("list my debtors"))
check_false(" I owe John 50 (record, not ask)", utility.is_debt_query("I owe John 50"))
check_false(" Chipo owes me 20 for bread", utility.is_debt_query("Chipo owes me 20 for bread"))
check_false(" sold 2 jam", utility.is_debt_query("sold 2 jam"))
print("== Bugs 4+5: canonical report format ==")
custs = [{"name": "Supplier A", "payable": 50.0, "receivable": 0},
{"name": "Supplier B", "payable": 75.0, "receivable": 0},
{"name": "Chipo", "payable": 0, "receivable": 20.0}]
rep = utility._handle_customer_credit_query("who do i owe money?", custs, "USD")
print(rep); print("---")
check_true(" has You owe heading", rep.startswith("*You owe:*"))
check_true(" has em-dash lines", "Supplier A — USD50.00" in rep)
check_true(" has total", "*Total you owe:* USD125.00" in rep)
rep2 = utility._handle_customer_credit_query("who owes me money?", custs, "USD")
check_true(" receivables heading", rep2.startswith("*Who owes you:*"))
check_true(" receivable line", "Chipo — USD20.00" in rep2)
check_true(" receivable total", "*Total owed to you:* USD20.00" in rep2)
rep3 = utility._handle_customer_credit_query("who owes me and who do i owe", custs, "USD")
check_true(" combined both", ("*Who owes you:*" in rep3) and ("*You owe:*" in rep3))
print("== Bug 3: money-mention provenance ==")
check_false(" sold 40 tomatoes to Chipo", utility._has_money_mention("I sold 40 tomatoes to Chipo"))
check_true(" sold 2 jam for 4", utility._has_money_mention("sold 2 jam for 4"))
check_true(" sold 2 jam at 3 each", utility._has_money_mention("sold 2 jam at 3 each"))
check_true(" USD4", utility._has_money_mention("sold jam USD4"))
check_true(" 40 dollars", utility._has_money_mention("sold tomatoes 40 dollars"))
check_true(" paid 20", utility._has_money_mention("sold jam, she paid 20"))
print("== Bug 3: promo price wins over guessed amount (resolve_sale_prices) ==")
class _Doc:
def __init__(self, d): self._d = d
def to_dict(self): return self._d
class _Q:
def __init__(self, docs): self._docs = docs
def where(self, *a, **k): return self
def get(self): return self._docs
class _Coll:
def __init__(self, name_map): self.name_map = name_map
def document(self, _): return self
def collection(self, name): return _Q(self.name_map.get(name, []))
promo_db = mock.MagicMock()
promo_db.collection.return_value = _Coll({
"stock_batches": [_Doc({"name": "tomato", "on_sale": True, "sale_price_each": 0.51,
"quantity_remaining": 200, "price_each": 0.51})],
"price_overrides": [],
})
txns = [{"intent": "create", "transaction_type": "sale",
"details": {"items": [{"item": "tomato", "quantity": 40, "unit": "each"}],
"amount": 40.0, "customer": "Chipo"}}]
utility.resolve_sale_prices(promo_db, "263771", txns, raw_text="I sold 40 tomatoes to Chipo")
d = txns[0]["details"]; it = d["items"][0]
check(" promo ppu", it.get("price_per_unit"), 0.51)
check(" promo total", d.get("amount"), 20.4)
check_true(" promo flag survives _infer_prices", it.get("promo_applied"))
# Explicit user price must NOT be overridden by the promo.
txns2 = [{"intent": "create", "transaction_type": "sale",
"details": {"items": [{"item": "tomato", "quantity": 40, "unit": "each",
"price_per_unit": 1.0}], "amount": 40.0}}]
utility.resolve_sale_prices(promo_db, "263771", txns2, raw_text="I sold 40 tomatoes for $1 each")
check(" explicit price kept", txns2[0]["details"]["items"][0].get("price_per_unit"), 1.0)
check_true(" explicit flag set", txns2[0]["details"].get("explicit_price"))
print("== Bug 6: stock-in cost prompt + remembered-cost correction ==")
stock_txn = [{"intent": "create", "transaction_type": "stock_in",
"details": {"items": [{"item": "jam", "quantity": 100, "unit": "each"}]}}]
prompt = main._check_missing_details(stock_txn, currency="USD")
print(prompt); print("---")
check_true(" asks cost for jam", prompt is not None and "pay for the Jam" in prompt)
stock_ok = [{"intent": "create", "transaction_type": "stock_in",
"details": {"items": [{"item": "orange", "quantity": 100, "unit": "each",
"price_per_unit": 0.15}]}}]
check(" no prompt when cost known", main._check_missing_details(stock_ok, currency="USD"), None)
remembered = [{"intent": "create", "transaction_type": "stock_in",
"details": {"items": [{"item": "orange", "quantity": 100, "unit": "each",
"price_per_unit": 0.15, "cost_source": "remembered"}],
"amount": 15.0}}]
res = main._try_fill_missing_details("0.20 each", remembered, currency="USD", mobile="")
check_true(" correction accepted", res is not None)
if res:
filled, still = res
it = filled[0]["details"]["items"][0]
check(" corrected ppu", it.get("price_per_unit"), 0.2)
check(" corrected total", filled[0]["details"].get("amount"), 20.0)
check(" nothing still missing", still, None)
print("== card shows remembered-cost note ==")
card = utility.format_transaction_response([{
"intent": "create", "transaction_type": "stock_in",
"details": {"items": [{"item": "orange", "quantity": 100, "unit": "each",
"price_per_unit": 0.15, "cost_source": "remembered"}],
"currency": "USD"}}])
print(card); print("---")
check_true(" last-cost marker", "your last cost" in card)
check_true(" changed-cost hint", "reply with the new cost" in card)
card2 = utility.format_transaction_response([{
"intent": "create", "transaction_type": "sale",
"details": {"items": [{"item": "tomato", "quantity": 40, "unit": "each",
"price_per_unit": 0.51, "promo_applied": True}],
"currency": "USD"}}])
print(card2); print("---")
check_true(" on-sale marker", "on sale" in card2)
check_false(" no raw flag leak", "Promo Applied" in card2 or "Explicit Price" in card2)
print("== _money_to_float equivalence ==")
for v, want in [("USD0.15", 0.15), ("0.15 USD", 0.15), ("0.15 dollars", 0.15),
("15 cents", 0.15), ("50c", 0.5), ("R50", 50.0), ("4", 4.0)]:
check(f" {v}", utility._money_to_float(v), want)
print("== misc: normalise, debt-regex safety ==")
check(" carrots→carrot", utility.normalise_item_name("Carrots"), "carrot")
check(" tomatoes→tomato", utility.normalise_item_name("tomatoes"), "tomato")
print(f"\n{'='*40}\nTOTAL: {PASS} passed, {FAIL} failed")
sys.exit(1 if FAIL else 0)