def test_floor_division() -> None: m1 = Money("100", currency="SEK") assert isinstance(m1, Money) assert m1.amount == 100 assert m1.currency == "SEK" assert str(m1) == "100.00 SEK" m2 = m1 // 3 assert isinstance(m2, Money) assert m2.amount == 33 assert m2.currency == "SEK" assert str(m2) == "33.00 SEK" m2 = m1 // "3 SEK" assert isinstance(m2, Money) assert m2.amount == 33 assert m2.currency is None assert str(m2) == "33.00" with pytest.raises(ZeroDivisionError): m1 // 0 m3 = Money("10.39", currency="USD") full_usd_amounts = m1 // m3 assert isinstance(full_usd_amounts, Money) assert full_usd_amounts == 9 assert full_usd_amounts.currency is None assert str(full_usd_amounts) == "9.00"
def test_rate(): assert Rate(100) == 100 assert Rate("100.50551") == ExchangeRate("100.50551") assert str(Rate("4711.1338")) == "4711.1338" assert Rate(100).currency is None assert Rate(100).currency_code is None assert Rate(100).amount == 100 assert Rate(100).value == "100.00" assert Rate(50) < 51 assert Rate(50) > 49 assert Rate(50) > Rate(49) assert Rate(50) + Rate(50) == 100 assert (Rate(50) + Rate(50)).__class__ is Rate assert str(Rate(50) + Rate(50)) == "100.00" assert repr(Rate(50) + Rate(50)) == '<stockholm.Rate: "100.00">' assert (Rate(50) + Rate(50) + Money(50)).__class__ is Money assert str(Rate(50) + Rate(50) + Money(50)) == "150.00" assert repr(Rate(50) + Rate(50) + Money(50)) == '<stockholm.Money: "150.00">' assert Rate(Money(100)) == Rate(100) assert Rate(Money(100)).__class__ is Rate
def test_complex_addition() -> None: m = 1000 + 500 + Money(150, currency="NOK") + "4711.15000" + Money( -10000) + Money("55.70 NOK") + Decimal("25.01") assert isinstance(m, Money) assert m.amount == Decimal("-3558.14") assert m.currency == "NOK" assert str(m) == "-3558.14 NOK"
def test_custom_currency(): class EUR(BaseCurrency): pass c1 = Currency("EUR") assert c1 != Money(0, EUR) assert c1.decimal_digits == 2 c2 = Currency(c1) assert c2.ticker == "EUR" assert c2 == c1 assert str(c2) == "EUR" assert c2 == "EUR" c3 = Currency() assert c3.ticker == "" assert c3 != c1 assert c3 == "" assert str(c3) == "" assert c3 != Money(0, EUR) c4 = Currency(EUR) assert c4.ticker == "EUR" assert c4 == c1 assert c4 == "EUR" CARLOS = Currency("CarlosCoin", decimal_digits=0) assert CARLOS.ticker == "CarlosCoin" m = Money(100, CARLOS) assert str(m) == "100 CarlosCoin"
def test_simple_multiplication() -> None: m1 = Money("333.3333", currency="SEK") assert isinstance(m1, Money) assert m1.amount == Decimal("333.3333") assert m1.currency == "SEK" assert str(m1) == "333.3333 SEK" m2 = m1 * 3 assert isinstance(m2, Money) assert m2.amount == Decimal("999.9999") assert m2.currency == "SEK" assert str(m2) == "999.9999 SEK" m2 = 3 * m1 assert isinstance(m2, Money) assert m2.amount == Decimal("999.9999") assert m2.currency == "SEK" assert str(m2) == "999.9999 SEK" m2 = m1 * Money(3) assert isinstance(m2, Money) assert m2.amount == Decimal("999.9999") assert m2.currency == "SEK" assert str(m2) == "999.9999 SEK" with pytest.raises(InvalidOperandError): m1 * m1
def test_dumb_object_input() -> None: class ThirdPartyMoney: amount: Any currency: Optional[str] output: Optional[str] def __init__(self, amount: Any, currency: Optional[str] = None, output: Optional[str] = None) -> None: self.amount = amount self.currency = currency self.output = output def __str__(self) -> str: if self.output: return self.output amount = self.amount if self.currency: return f"{self.currency} {amount:.2f}" return f"{amount}" m = Money(ThirdPartyMoney("1338.4711", "EUR")) assert m.amount == Decimal("1338.4711") assert str(m) == "1338.4711 EUR" m = Money(ThirdPartyMoney(50)) assert m.amount == Decimal(50) assert str(m) == "50.00" with pytest.raises(ConversionError): Money(ThirdPartyMoney(None)) m = Money(ThirdPartyMoney(1, output="1338.4711 EUR")) assert m.amount == Decimal("1.00") assert str(m) == "1.00 EUR" m = Money(ThirdPartyMoney(1, output="1338.4711")) assert m.amount == Decimal("1.00") assert str(m) == "1.00" m = Money(ThirdPartyMoney(1, output="1338.4711"), currency="SEK") assert m.amount == Decimal("1.00") assert str(m) == "1.00 SEK" m = Money(ThirdPartyMoney(1, output="USD 1338.4711")) assert m.amount == Decimal("1.00") assert str(m) == "1.00 USD" m = Money(ThirdPartyMoney(1, output="USD USD")) assert m.amount == Decimal("1.00") assert str(m) == "1.00" m = Money(ThirdPartyMoney(None, output="1338.4711 EUR")) assert m.amount == Decimal("1338.4711") assert str(m) == "1338.4711 EUR" with pytest.raises(ConversionError): Money(ThirdPartyMoney(None, output="USD USD"))
def test_sum_list_cent_values() -> None: values = [471100, 10000, 509000, 350200, "313450", "900400", "1000", "100", 13999] m = Money.sum(values, currency="SEK", from_sub_units=True) assert isinstance(m, Money) assert m == Money(2569249, currency="SEK", from_sub_units=True) assert m == Money("25692.49 SEK") assert str(m) == "25692.49 SEK"
def test_from_json(): input_value = '{"value": "13384711 JPY", "units": 13384711, "nanos": 0, "currency_code": "JPY"}' assert str(Money.from_json(input_value)) == "13384711.00 JPY" assert str(Money.from_json(input_value).to_currency( get_currency("JPY"))) == "13384711 JPY" assert str(Money.from_json(input_value).to_currency( get_currency("SEK"))) == "13384711.00 SEK" assert str(Money(input_value)) == "13384711.00 JPY" assert str(Money(input_value, currency=get_currency("JPY"))) == "13384711 JPY"
def test_object_input() -> None: class ThirdPartyMoney: amount: int currency: Optional[str] def __init__(self, amount: str, currency: Optional[str] = None) -> None: self.amount = Decimal(amount) self.currency = currency def __str__(self) -> str: amount = self.amount if self.currency: return f"{self.currency} {amount:.2f}" return f"{amount:.2f}" m = Money(ThirdPartyMoney("0")) assert m.amount == Decimal("0") assert str(m) == "0.00" m = Money(ThirdPartyMoney("1")) assert m.amount == Decimal("1") assert str(m) == "1.00" m = Money(ThirdPartyMoney("1.50")) assert m.amount == Decimal("1.50") assert str(m) == "1.50" m = Money(ThirdPartyMoney("1.5000")) assert m.amount == Decimal("1.5000") assert str(m) == "1.50" m = Money(ThirdPartyMoney("1.3333")) assert m.amount == Decimal("1.3333") assert str(m) == "1.3333" m = Money(ThirdPartyMoney("1.6666")) assert m.amount == Decimal("1.6666") assert str(m) == "1.6666" m = Money(ThirdPartyMoney("1.6666", "SEK")) assert m.amount == Decimal("1.6666") assert str(m) == "1.6666 SEK" m = Money(ThirdPartyMoney("1.6666", "SEK"), currency="SEK") assert m.amount == Decimal("1.6666") assert str(m) == "1.6666 SEK" m = Money(ThirdPartyMoney("1.6666"), currency="SEK") assert m.amount == Decimal("1.6666") assert str(m) == "1.6666 SEK" with pytest.raises(ConversionError): Money(ThirdPartyMoney("1.6666", "USD"), currency="SEK")
def test_string_formatting_sentence_currency_types() -> None: m1 = Money(1352953, stockholm.currency.JPY) exchange_rate = Decimal("0.08861326") m2 = round(Money(m1 * exchange_rate, stockholm.currency.SEK), 2) expected = ( "I have 1,352,953 JPY which equals around 119,889.58 SEK if the exchange rate is 0.08861326 (JPY -> SEK)." ) assert ( f"I have {m1:,m} which equals around {m2:,m} if the exchange rate is {exchange_rate} ({m1:c} -> {m2:c})." == expected)
def test_string_formatting_sentence() -> None: m1 = Money(1352953, "JPY") exchange_rate = Decimal("0.08861326") m2 = Money(m1 * exchange_rate, "SEK") expected = ( "I have 1,352,953 JPY which equals around 119,889.58 SEK if the exchange rate is 0.08861326 (JPY -> SEK)." ) assert ( f"I have {m1:,.0m} which equals around {m2:,.2m} if the exchange rate is {exchange_rate} ({m1:c} -> {m2:c})." == expected)
def test_currency_change() -> None: m = Money("123456.50", currency="GBP") assert m == "123456.50 GBP" assert m.to("SEK") == "123456.50 SEK" assert m.to_currency("SEK") == "123456.50 SEK" assert m.to_currency(Currency.SEK) == "123456.50 SEK" assert m.to_currency(Currency.JPY) == "123456.50 JPY" assert str(m.to_currency(Currency.JPY)) == "123456.5 JPY" assert str(m.to_currency(None)) == "123456.50" assert str(Money(m)) == "123456.50 GBP" assert str(Money(m, units=123456, nanos=500000000)) == "123456.50 GBP" assert str(Money(m, currency=None, units=123456, nanos=500000000)) == "123456.50"
def test_asdict(): assert Money(1338, currency=Currency.SEK).asdict() == { "value": "1338.00 SEK", "units": 1338, "nanos": 0, "currency_code": "SEK", } assert Money("1338.4711", currency=Currency.SEK).as_dict() == { "value": "1338.4711 SEK", "units": 1338, "nanos": 471100000, "currency_code": "SEK", } assert dict(Money("0.123456", currency="SEK")) == { "value": "0.123456 SEK", "units": 0, "nanos": 123456000, "currency_code": "SEK", } assert dict(Money("0.1", currency=Currency("SEK"))) == { "value": "0.10 SEK", "units": 0, "nanos": 100000000, "currency_code": "SEK", } assert Money(1338, currency=Currency.SEK).keys() == [ "value", "units", "nanos", "currency_code" ] assert Money(1338, currency=Currency.SEK)["units"] == 1338 assert Money(1338, currency=Currency.SEK)["value"] == "1338.00 SEK" with pytest.raises(KeyError): Money(1338, currency=Currency.SEK)["does_not_exist"]
def test_protobuf_input( money_input: Any, str_output: str, units: int, nanos: int, proto_currency: str, currency_code: Optional[str], decimal_input: str, protobuf_bytes: bytes, ) -> None: m1 = Money(money_input) proto_output = m1.as_protobuf() assert proto_output.units == units assert proto_output.nanos == nanos assert proto_output.currency_code is not None assert proto_output.currency_code == proto_currency assert proto_output.SerializeToString() == protobuf_bytes m2 = Money(proto_output.SerializeToString()) assert str(m2) == str_output assert m2.as_decimal() == Decimal(decimal_input) assert m2.units == units assert m2.nanos == nanos if currency_code is None: assert m2.currency is None else: assert m2.currency == currency_code if currency_code is None: assert m2.currency is None else: assert m2.currency_code == currency_code assert m2.as_protobuf().currency_code == proto_currency m3 = Money(proto_output) assert str(m3) == str_output assert m3.as_decimal() == Decimal(decimal_input) assert m3.units == units assert m3.nanos == nanos if currency_code is None: assert m3.currency is None else: assert m3.currency == currency_code if currency_code is None: assert m3.currency is None else: assert m3.currency_code == currency_code assert m3.as_protobuf().currency_code == proto_currency assert m3 == m2
def test_from_dict(): input_dict = { "value": "13384711 JPY", "units": 13384711, "nanos": 0, "currency_code": "JPY" } assert str(Money.from_dict(input_dict)) == "13384711.00 JPY" assert str( Money.from_dict(input_dict).to_currency( get_currency(input_dict.get("currency_code")))) == "13384711 JPY" assert str(Money(input_dict)) == "13384711.00 JPY" assert str( Money(input_dict, currency=get_currency( input_dict.get("currency_code")))) == "13384711 JPY"
def test_from_protobuf(): input_value = b"\n\x03USD\x10\x90\xf0\x84\x02\x18\x80\xbc\xe7\xd1\x01" assert str(Money.from_protobuf(input_value)) == "4274192.44 USD" assert str(Money.from_proto(input_value)) == "4274192.44 USD" assert str(Money.from_protobuf(input_value).to_currency( "SEK")) == "4274192.44 SEK" assert str(Money(input_value)) == "4274192.44 USD"
def test_sort_with_differing_currencies() -> None: lst = [ Money("1", currency="SEK"), Money("-1", currency="USD"), ] with pytest.raises(CurrencyMismatchError): lst.sort() with pytest.raises(CurrencyMismatchError): sorted(lst) with pytest.raises(CurrencyMismatchError): sorted(lst, key=Money) with pytest.raises(CurrencyMismatchError): Money.sort(lst)
def test_default_currency(): with pytest.raises(TypeError): DefaultCurrency() m1 = Money(4711) m2 = Money(4711, DefaultCurrency) m3 = Money(4711, currency=DefaultCurrency) assert m1 == m2 == m3 assert m1.currency is None assert m2.currency is None assert m3.currency is None assert m1.currency_code is None assert m2.currency_code is None assert m3.currency_code is None
def test_info_methods() -> None: m = Money("4711.75", currency="EUR") assert m.amount == Decimal("4711.75") assert m.currency == "EUR" assert m.units == 4711 assert m.nanos == 750000000 assert m.amount_as_string() == "4711.75" assert m.amount_as_string(min_decimals=5, max_decimals=8) == "4711.75000" assert m.amount_as_string(min_decimals=0, max_decimals=8) == "4711.75" assert m.amount_as_string(min_decimals=0, max_decimals=1) == "4711.8" assert m.amount_as_string(min_decimals=0, max_decimals=0) == "4712" assert m.amount_as_string(max_decimals=0) == "4712" assert m.amount_as_string(min_decimals=10) == "4711.7500000000" with pytest.raises(ValueError): m.amount_as_string(min_decimals=2, max_decimals=0) assert m.as_string() == "4711.75 EUR" assert m.as_str() == "4711.75 EUR" assert m.as_decimal() == Decimal("4711.75") assert m.as_int() == 4711 assert m.as_float() == float(m) assert m.is_zero() is False assert m.is_signed() is False assert Money(0).is_zero() is True assert Money(0).is_signed() is False assert Money(-1).is_signed() is True m = Money("-0.0000000001", currency="EUR") assert m.amount == Decimal("-0.0000000001") assert m.currency == "EUR" assert m.units == 0 assert m.nanos == 0 assert m.amount_as_string() == "0.00" assert m.amount_as_string(max_decimals=10) == "-0.0000000001" assert m.as_string() == "0.00 EUR" assert m.as_decimal() == Decimal("-0.0000000001") assert m.as_int() == 0 assert m.is_zero() is False assert m.is_signed() is True
def test_object_arithmetics() -> None: m = Money(0, currency="SEK") assert m.add(1).add(2).add(3) == Money(6, currency="SEK") assert m.add(10).sub(5) == Money(5, currency="SEK") assert m.add(10).subtract(5) == Money(5, currency="SEK") with pytest.raises(Exception): assert m.add(1).add(2).add(3) == Money(6, currency="EUR") m2 = Money(471100, from_sub_units=True) assert m2.add(133800, from_sub_units=True) == Money(604900, from_sub_units=True) assert m2.add(133800, from_sub_units=True) == Money("6049.00")
def test_currency(): EUR = Currency("EUR") assert str(EUR) == "EUR" assert repr(EUR) == '<stockholm.Currency: "EUR">' m = Money(100, EUR) assert str(m) == "100.00 EUR" assert isinstance(m.currency, Currency) assert str(m.currency) == "EUR" assert str(m.currency.ticker) == "EUR"
def test_divmod() -> None: m1 = Money("49", currency="SEK") assert isinstance(m1, Money) assert m1.amount == 49 assert m1.currency == "SEK" assert str(m1) == "49.00 SEK" m2, m3 = divmod(m1, 14) assert isinstance(m2, Money) assert m2.amount == 3 assert m2.currency == "SEK" assert str(m2) == "3.00 SEK" assert isinstance(m3, Money) assert m3.amount == 7 assert m3.currency == "SEK" assert str(m3) == "7.00 SEK" m3, m4 = divmod(m1, Money(14, currency="USD")) assert isinstance(m3, Money) assert m3.amount == 3 assert m3.currency is None assert str(m3) == "3.00" assert isinstance(m4, Money) assert m4.amount == 7 assert m4.currency == "SEK" assert str(m4) == "7.00 SEK" m5, m6 = divmod(m1, Money(14, currency="SEK")) assert isinstance(m5, Money) assert m5.amount == 3 assert m5.currency is None assert str(m5) == "3.00" assert isinstance(m6, Money) assert m6.amount == 7 assert m6.currency == "SEK" assert str(m6) == "7.00 SEK"
def test_bad_values() -> None: m = Money(1, currency="SEK") with pytest.raises(InvalidOperandError): m + "5,0" with pytest.raises(InvalidOperandError): m + "USD USD" with pytest.raises(InvalidOperandError): m - "50 000"
def test_currency_arithmetics(): EUR = Currency("EUR") SEK = Currency("SEK") assert EUR assert EUR == "EUR" assert EUR != "SEK" assert EUR != SEK assert EUR != "" assert EUR != 0 assert EUR is not False m1 = Money(100, EUR) m2 = Money(100, "EUR") assert m1 == m2 m3 = Money(100, "SEK") assert m1 != m3 m4 = Money(100, SEK) assert m1 != m4 assert m3 == m4
def test_conversion_extensions() -> None: m1 = Money(50, currency="USD") m2 = Money(-50, currency="USD") assert m1 == (-m2) assert (-m1) == m2 assert (+m1) == m1 assert isinstance(abs(m1), Money) assert abs(m1) == m1 assert abs(m2) != m2 assert abs(m2) == m1 assert isinstance(int(m1), int) assert int(m1) == 50 assert int(m2) == -50 assert isinstance(float(m1), float) assert float(m1) == 50.00 assert float(m2) == -50.00
def test_value_input() -> None: assert str(Money(value="4711.00 SEK")) == "4711.00 SEK" assert str(Money(value="4711.00 SEK", currency_code="SEK")) == "4711.00 SEK" assert str(Money(value="4711.00 SEK", currency=Currency.SEK)) == "4711.00 SEK" assert str( Money(value="4711.00 SEK", currency=Currency.SEK, currency_code="SEK")) == "4711.00 SEK" assert str(Money(value="4711.00", currency_code="SEK")) == "4711.00 SEK" assert str(Money(value="4711.00", currency=Currency.SEK)) == "4711.00 SEK" assert str(Money(value="4711.00")) == "4711.00" with pytest.raises(ConversionError): assert Money(value="4711.00 SEK", currency_code="JPY") with pytest.raises(ConversionError): assert Money(value="4711.00 SEK", currency=Currency.JPY)
def test_true_division() -> None: m1 = Money("100", currency="SEK") assert isinstance(m1, Money) assert m1.amount == 100 assert m1.currency == "SEK" assert str(m1) == "100.00 SEK" m2 = m1 / 3 assert isinstance(m2, Money) assert round(m2.amount, 9) == Decimal("33.333333333") assert m2.currency == "SEK" assert str(m2) == "33.333333333 SEK" with pytest.raises(ZeroDivisionError): m1 / 0 m3 = Money("10.39", currency="USD") exchange_rate = m1 / m3 assert isinstance(exchange_rate, Money) assert round(exchange_rate, 2) == Decimal("9.62") assert exchange_rate.currency is None assert str(exchange_rate) == "9.624639076"
def test_metacurrency(): class EUR(Currency): pass class SEK(Currency): pass class DogeCoin(Currency): ticker = "DOGE" class AppleStock(Currency): ticker = "APPL" EUR2 = Currency("EUR") assert EUR assert EUR == "EUR" assert EUR != "EUR2" assert EUR == EUR2 assert EUR != SEK m1 = Money(100, EUR) m2 = Money(100, "EUR") m3 = Money(100, EUR2) assert m1 == m2 assert m1 == m3 stock = Money(5, AppleStock) assert stock == "5.00 APPL" assert stock > 0 assert stock < 10 assert str(stock) == "5.00 APPL" assert f"{stock:c}" == "APPL" assert str(EUR) == "EUR" assert str(SEK) == "SEK" assert str(AppleStock) == "APPL" assert repr(EUR) == '<stockholm.Currency: "EUR">' assert repr(AppleStock) == '<stockholm.Currency: "APPL">' assert EUR is not None assert EUR != "" assert EUR != 0 assert EUR != Money(0, EUR) class CurrencyConcept(Currency): ticker = None assert not bool(CurrencyConcept) assert CurrencyConcept != EUR assert CurrencyConcept == CurrencyConcept assert not CurrencyConcept assert CurrencyConcept is not None assert CurrencyConcept is not False assert CurrencyConcept is not True assert CurrencyConcept == "" assert CurrencyConcept != 0 assert CurrencyConcept != Money(0, EUR)
def test_pow() -> None: m1 = Money("2", currency="BIT") assert isinstance(m1, Money) assert m1.amount == 2 assert m1.currency == "BIT" assert str(m1) == "2.00 BIT" m2 = m1**4 assert isinstance(m2, Money) assert m2.amount == 16 assert m2.currency == "BIT" assert str(m2) == "16.00 BIT" m2 = m1**Money(4) assert isinstance(m2, Money) assert m2.amount == 16 assert m2.currency == "BIT" assert str(m2) == "16.00 BIT" with pytest.raises(InvalidOperandError): m1**m1 assert Money(2)**Money(4) == 16
def test_input_values(amount: Any, currency: Any, exception_expected: Optional[Exception]) -> None: try: m = Money(amount, currency=currency) if exception_expected: assert False, "Exception expected" assert m is not None except Exception as ex: if not exception_expected: raise if not isinstance(ex, exception_expected): raise assert True