New test files: - test_onboarding_service.py: 30 tests for vendor onboarding flow - test_team_service.py: 11 tests for team management - test_capacity_forecast_service.py: 14 tests for capacity forecasting - test_i18n.py: 50+ tests for internationalization - test_money.py: 37 tests for money handling utilities Coverage improved from 67.09% to 69.06% 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
308 lines
9.3 KiB
Python
308 lines
9.3 KiB
Python
# 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
|