Files
Meal-Planner/backend/tests/test_units.py
T
admin fd8ba3c4d2 feat(backend): implement unit conversion for cost calculation
- Add UnitConverter (normalization, within-family, density tables)
- Update cost.py to convert recipe qty to grocery price unit
- Update generate.py _load_match_index to fetch ingredient name + unit
- Fix orchestrator email/shopping-list cost loops to use conversion
- Fix missing Ingredient import in generate.py
- Add 19 unit tests
2026-05-24 13:46:50 -07:00

106 lines
3.3 KiB
Python

"""Tests for app.utils.units."""
from decimal import Decimal
import pytest
from app.utils.units import UnitConverter, convert_qty, normalize_unit
class TestNormalizeUnit:
def test_synonyms(self):
assert normalize_unit("pounds") == "lb"
assert normalize_unit("OUNCE") == "oz"
assert normalize_unit("Cups") == "cup"
assert normalize_unit("TBS") == "tbsp"
assert normalize_unit("each") == "ea"
assert normalize_unit("doz") == "dozen"
def test_already_canonical(self):
assert normalize_unit("lb") == "lb"
assert normalize_unit("cup") == "cup"
assert normalize_unit("ea") == "ea"
def test_unknown_passthrough(self):
assert normalize_unit("bunch") == "bunch"
assert normalize_unit("can") == "can"
def test_none(self):
assert normalize_unit(None) == "unknown"
assert normalize_unit("") == "unknown"
class TestWithinFamilyConversion:
def test_weight_lb_to_oz(self):
assert convert_qty(2.0, "lb", "oz") == Decimal("32")
def test_weight_oz_to_lb(self):
result = convert_qty(16.0, "oz", "lb")
assert result == Decimal("1.0000")
def test_volume_cup_to_tbsp(self):
result = convert_qty(1.0, "cup", "tbsp")
assert result == Decimal("16")
def test_volume_tbsp_to_tsp(self):
result = convert_qty(1.0, "tbsp", "tsp")
assert result == Decimal("3")
def test_count_dozen_to_ea(self):
result = convert_qty(1.0, "dozen", "ea")
assert result == Decimal("12")
def test_same_unit(self):
assert convert_qty(5.0, "lb", "lb") == Decimal("5")
class TestDensityConversion:
def test_olive_oil_tbsp_to_cup(self):
# 1 cup = 16 tbsp
result = convert_qty(
1.0, "cup", "tbsp", ingredient_name_lower="olive oil"
)
assert result == Decimal("16")
def test_rice_cup_to_lb(self):
# 1 cup rice = 185 g; 1 lb = 453.592 g → 1 cup = 185/453.592 ≈ 0.4079 lb
result = convert_qty(
1.0, "cup", "lb", ingredient_name_lower="rice, long-grain white"
)
assert result == Decimal("0.4079")
def test_butter_tbsp_to_cup(self):
result = convert_qty(
1.0, "cup", "tbsp", ingredient_name_lower="butter, unsalted"
)
assert result == Decimal("16")
def test_seed_size_ea_to_lb(self):
# 1 avocado ≈ 170 g = 0.3748 lb
result = convert_qty(
1.0, "ea", "lb", ingredient_name_lower="avocado"
)
assert result == Decimal("0.3748")
def test_unknown_density_falls_back(self):
# No density for "dragon fruit" → fallback to dimensionless qty
result = convert_qty(2.0, "cup", "lb", ingredient_name_lower="dragon fruit")
assert result == Decimal("2")
class TestFallback:
def test_incommensurable_units_without_name(self):
result = convert_qty(2.0, "cup", "lb")
assert result == Decimal("2")
def test_incommensurable_units_with_unmapped_name(self):
result = convert_qty(2.0, "cup", "lb", ingredient_name_lower="xyz")
assert result == Decimal("2")
def test_missing_from_unit(self):
result = convert_qty(2.0, None, "lb")
assert result == Decimal("2")
def test_missing_to_unit(self):
result = convert_qty(2.0, "lb", None)
assert result == Decimal("2")