# tests/unit/utils/test_money.py """ Unit tests for money handling utilities. Tests cover: - Euro to cents conversion - Cents to euros conversion - Price string parsing - Money class formatting - Arithmetic operations """ from decimal import Decimal import pytest from app.utils.money import ( CURRENCY_DECIMALS, DEFAULT_CURRENCY, Money, cents_to_euros, euros_to_cents, parse_price_to_cents, ) @pytest.mark.unit @pytest.mark.utils class TestEurosToCents: """Test euros_to_cents conversion""" def test_float_conversion(self): """Test converting float to cents""" assert euros_to_cents(105.91) == 10591 assert euros_to_cents(19.99) == 1999 assert euros_to_cents(0.01) == 1 assert euros_to_cents(0.0) == 0 def test_string_conversion(self): """Test converting string to cents""" assert euros_to_cents("105.91") == 10591 assert euros_to_cents("19.99") == 1999 assert euros_to_cents("0.01") == 1 def test_string_with_currency(self): """Test converting string with currency symbols""" assert euros_to_cents("€105.91") == 10591 assert euros_to_cents("$ 19.99") == 1999 assert euros_to_cents("£0.01") == 1 def test_string_with_comma_decimal(self): """Test converting European-style decimal comma""" assert euros_to_cents("105,91") == 10591 assert euros_to_cents("19,99") == 1999 def test_decimal_conversion(self): """Test converting Decimal to cents""" assert euros_to_cents(Decimal("105.91")) == 10591 assert euros_to_cents(Decimal("19.99")) == 1999 def test_integer_conversion(self): """Test converting integer euros to cents""" assert euros_to_cents(100) == 10000 assert euros_to_cents(1) == 100 def test_none_returns_zero(self): """Test None returns 0""" assert euros_to_cents(None) == 0 def test_rounding(self): """Test rounding behavior""" # ROUND_HALF_UP: 0.5 rounds up assert euros_to_cents("0.995") == 100 # Rounds up assert euros_to_cents("0.994") == 99 # Rounds down @pytest.mark.unit @pytest.mark.utils class TestCentsToEuros: """Test cents_to_euros conversion""" def test_basic_conversion(self): """Test basic cents to euros conversion""" assert cents_to_euros(10591) == 105.91 assert cents_to_euros(1999) == 19.99 assert cents_to_euros(1) == 0.01 assert cents_to_euros(0) == 0.0 def test_none_returns_zero(self): """Test None returns 0.0""" assert cents_to_euros(None) == 0.0 def test_large_amounts(self): """Test large amounts""" assert cents_to_euros(10000000) == 100000.0 # 100k euros assert cents_to_euros(999999999) == 9999999.99 @pytest.mark.unit @pytest.mark.utils class TestParsePriceToCents: """Test parse_price_to_cents function""" def test_parse_simple_euro(self): """Test parsing simple EUR prices""" cents, currency = parse_price_to_cents("19.99 EUR") assert cents == 1999 assert currency == "EUR" def test_parse_euro_symbol(self): """Test parsing with € symbol""" cents, currency = parse_price_to_cents("19,99 €") assert cents == 1999 assert currency == "EUR" def test_parse_usd(self): """Test parsing USD prices""" cents, currency = parse_price_to_cents("19.99 USD") assert cents == 1999 assert currency == "USD" def test_parse_dollar_symbol(self): """Test parsing with $ symbol""" cents, currency = parse_price_to_cents("$19.99") assert cents == 1999 assert currency == "USD" def test_parse_gbp(self): """Test parsing GBP prices""" cents, currency = parse_price_to_cents("£19.99") assert cents == 1999 assert currency == "GBP" def test_parse_chf(self): """Test parsing CHF prices""" cents, currency = parse_price_to_cents("19.99 CHF") assert cents == 1999 assert currency == "CHF" def test_parse_numeric_float(self): """Test parsing numeric float""" cents, currency = parse_price_to_cents(19.99) assert cents == 1999 assert currency == DEFAULT_CURRENCY def test_parse_numeric_int(self): """Test parsing numeric int""" cents, currency = parse_price_to_cents(20) assert cents == 2000 assert currency == DEFAULT_CURRENCY def test_parse_none(self): """Test parsing None""" cents, currency = parse_price_to_cents(None) assert cents == 0 assert currency == DEFAULT_CURRENCY def test_parse_thousand_separator(self): """Test parsing with thousand separators""" cents, currency = parse_price_to_cents("1.000,00 EUR") assert cents == 100000 assert currency == "EUR" @pytest.mark.unit @pytest.mark.utils class TestMoneyClass: """Test Money class methods""" def test_from_euros(self): """Test Money.from_euros""" assert Money.from_euros(105.91) == 10591 assert Money.from_euros("19.99") == 1999 assert Money.from_euros(None) == 0 def test_from_cents(self): """Test Money.from_cents""" assert Money.from_cents(10591) == 10591 assert Money.from_cents(None) == 0 def test_to_euros(self): """Test Money.to_euros""" assert Money.to_euros(10591) == 105.91 assert Money.to_euros(None) == 0.0 @pytest.mark.unit @pytest.mark.utils class TestMoneyFormat: """Test Money.format method""" def test_format_basic(self): """Test basic formatting""" assert Money.format(10591) == "105.91" assert Money.format(1999) == "19.99" assert Money.format(1) == "0.01" assert Money.format(0) == "0.00" def test_format_with_currency(self): """Test formatting with currency""" assert Money.format(10591, "EUR") == "105.91 EUR" assert Money.format(1999, "USD") == "19.99 USD" def test_format_german_locale(self): """Test German locale formatting""" assert Money.format(10591, "", "de") == "105,91" assert Money.format(1000000, "", "de") == "10.000,00" def test_format_french_locale(self): """Test French locale formatting""" assert Money.format(10591, "", "fr") == "105,91" assert Money.format(1000000, "", "fr") == "10.000,00" def test_format_english_locale(self): """Test English locale formatting""" assert Money.format(10591, "", "en") == "105.91" assert Money.format(1000000, "", "en") == "10,000.00" def test_format_none(self): """Test formatting None""" assert Money.format(None) == "0.00" def test_format_large_amount(self): """Test formatting large amounts""" assert Money.format(10000000) == "100,000.00" @pytest.mark.unit @pytest.mark.utils class TestMoneyParse: """Test Money.parse method""" def test_parse_string(self): """Test parsing string""" assert Money.parse("19.99 EUR") == 1999 assert Money.parse("€105,91") == 10591 def test_parse_float(self): """Test parsing float""" assert Money.parse(19.99) == 1999 def test_parse_none(self): """Test parsing None""" assert Money.parse(None) == 0 @pytest.mark.unit @pytest.mark.utils class TestMoneyArithmetic: """Test Money arithmetic operations""" def test_add(self): """Test Money.add""" assert Money.add(1000, 500, 250) == 1750 assert Money.add(1000, None, 500) == 1500 assert Money.add() == 0 def test_subtract(self): """Test Money.subtract""" assert Money.subtract(1000, 500) == 500 assert Money.subtract(1000, 500, 250) == 250 assert Money.subtract(1000, None) == 1000 def test_multiply(self): """Test Money.multiply""" assert Money.multiply(1999, 3) == 5997 assert Money.multiply(1000, 0) == 0 def test_calculate_line_total(self): """Test Money.calculate_line_total""" assert Money.calculate_line_total(1999, 5) == 9995 assert Money.calculate_line_total(500, 10) == 5000 def test_calculate_order_total(self): """Test Money.calculate_order_total""" # Subtotal 100€, Tax 17€, Shipping 5€, Discount 10€ total = Money.calculate_order_total(10000, 1700, 500, 1000) assert total == 11200 # 100 + 17 + 5 - 10 = 112€ def test_calculate_order_total_no_extras(self): """Test order total without extras""" total = Money.calculate_order_total(10000) assert total == 10000 @pytest.mark.unit @pytest.mark.utils class TestCurrencyConfiguration: """Test currency configuration""" def test_default_currency(self): """Test default currency is EUR""" assert DEFAULT_CURRENCY == "EUR" def test_currency_decimals(self): """Test currency decimal places""" assert CURRENCY_DECIMALS["EUR"] == 2 assert CURRENCY_DECIMALS["USD"] == 2 assert CURRENCY_DECIMALS["GBP"] == 2 assert CURRENCY_DECIMALS["JPY"] == 0 # No decimals def test_supported_currencies(self): """Test supported currencies are defined""" assert "EUR" in CURRENCY_DECIMALS assert "USD" in CURRENCY_DECIMALS assert "GBP" in CURRENCY_DECIMALS assert "CHF" in CURRENCY_DECIMALS