def test_price_amount_to_string(self): m1 = SimpleMoneyModel(price=Money('200', 'JPY')) m2 = SimpleMoneyModel(price=Money('200.0', 'JPY')) assert str(m1.price) == '200 JPY' assert str(m1.price.amount) == '200' assert str(m2.price.amount) == '200.0'
def test_zero_edge_case(self): created = SimpleMoneyModel.objects.create(name='zero dollars', price=Money(0, 'USD')) assert created.price == Money(0, 'USD') ent = SimpleMoneyModel.objects.filter( price__exact=Money(0, 'USD')).get() assert ent.price == Money(0, 'USD')
def test_assign(self): price = Money(100, 'USD') ent = SimpleMoneyModel(name='test', price=price.amount, price_currency=price.currency) ent.save() assert ent.price == Money(100, 'USD') ent.price = Money(10, 'USD') ent.save() assert ent.price == Money(10, 'USD') ent_same = SimpleMoneyModel.objects.get(pk=ent.id) assert ent_same.price == Money(10, 'USD')
def test_instance_create_override_with_money(cls): overridden_value = Money('8765', 'GBP') instance = cls(value=overridden_value) assert instance.value != instance.expected_value() assert instance.value.amount != instance.expected_value().amount assert instance.value.currency != instance.expected_value().currency assert instance.value == overridden_value assert instance.value.amount == overridden_value.amount
def to_python(self, value): if isinstance(value, string_types): try: (value, currency) = value.split() if value and currency: return Money(value, currency) except ValueError: pass return value
def test_manager_create_override_with_money(cls): overridden_value = Money('9876', 'GBP') instance = cls.objects.create(value=overridden_value) assert instance.value != instance.expected_value() assert instance.value.amount != instance.expected_value().amount assert instance.value.currency != instance.expected_value().currency assert instance.value == overridden_value assert instance.value.amount == overridden_value.amount assert instance.value.currency == overridden_value.currency
def test_creating(self): ind = 0 for code, currency in CURRENCIES.items(): ind += 1 price = Money(ind * 1000.0, code) SimpleMoneyModel.objects.create(name=currency.name, price=price.amount, price_currency=price.currency) assert len(CURRENCIES) == SimpleMoneyModel.objects.all().count() for code in CURRENCIES: assert SimpleMoneyModel.objects.filter( price_currency=code).count() == 1
def test_retrive(self): price = Money(100, 'USD') SimpleMoneyModel.objects.create(name='one hundred dollars', price=price) # Filter qset = SimpleMoneyModel.objects.filter(price=price) assert qset.count() == 1 assert qset[0].price == price # Get entry = SimpleMoneyModel.objects.get(price=price) assert entry.price == price # test retrieving without currency entry = SimpleMoneyModel.objects.get(price=100) assert entry.price == price
def test_retrieve_and_update(self): created = SimpleMoneyModel.objects.create(name='one hundred dollars', price=Money(100, 'USD')) created.save() assert created.price == Money(100, 'USD') ent = SimpleMoneyModel.objects.filter( price__exact=Money(100, 'USD')).get() assert ent.price == Money(100, 'USD') ent.price = Money(300, 'USD') ent.save() ent = SimpleMoneyModel.objects.filter( price__exact=Money(300, 'USD')).get() assert ent.price == Money(300, 'USD')
def test_nullable_null_currency_vs_undefined_currency(self): name = 'test_nullable_null_currency_vs_undefined_currency' NullableMoneyModel.objects.create(name=name + '_null', price=None) NullableMoneyModel.objects.create(name=name + '_undefined', price=Money(0)) assert NullableMoneyModel.objects.all().count() == 2 # Assert NULL currency has 'blank' currency assert NullableMoneyModel.objects.filter( price__isnull=True).count() == 1 null_instance = NullableMoneyModel.objects.filter( price__isnull=True)[0] assert null_instance.name == name + '_null' null_instance = NullableMoneyModel.objects.filter(price_currency='')[0] assert null_instance.name == name + '_null' assert NullableMoneyModel.objects.filter( price__isnull=False).count() == 1 undefined_instance = NullableMoneyModel.objects.filter( price__isnull=False)[0] assert undefined_instance.name == name + '_undefined' undefined_instance = NullableMoneyModel.objects.filter( price_currency=settings.BASE_CURRENCY)[0] assert undefined_instance.name == name + '_undefined'
def test_mutation_of_amount_rounded_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.amount_rounded = 1
def test_mutation_of_currency_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.currency = 'USD'
def test_currency_attribute(self): value = Money(101, 'USD') assert value.currency == 'USD'
def __get__(self, obj, *args): amount, currency = self._get_values(obj) if amount is None: return None return Money(amount, currency)
def test_defaults_as_separate_values(self): ent = MoneyModelDefaults.objects.create(name='100 USD', price=100) assert ent.price == Money(100, 'USD') ent = MoneyModelDefaults.objects.get(pk=ent.id) assert ent.price == Money(100, 'USD')
def test_string_parse_default_currency(self): value = Money.from_string('100.35') assert value.amount == Decimal('100.35') assert value.currency == settings.BASE_CURRENCY assert value.currency == CURRENCIES[settings.BASE_CURRENCY]
def test_string_parse(self): value = Money.from_string('100.0 GBP') assert value.amount == Decimal('100.0') assert value.currency == 'GBP' assert value.currency == CURRENCIES['GBP']
def test_lookup(self): usd100 = Money(100, 'USD') eur100 = Money(100, 'EUR') uah100 = Money(100, 'UAH') SimpleMoneyModel.objects.create(name='one hundred dollars', price=usd100) SimpleMoneyModel.objects.create(name='one hundred and one dollars', price=usd100 + 1) SimpleMoneyModel.objects.create(name='ninety nine dollars', price=usd100 - 1) SimpleMoneyModel.objects.create(name='one hundred euros', price=eur100) SimpleMoneyModel.objects.create(name='one hundred and one euros', price=eur100 + 1) SimpleMoneyModel.objects.create(name='ninety nine euros', price=eur100 - 1) SimpleMoneyModel.objects.create(name='one hundred hrivnyas', price=uah100) SimpleMoneyModel.objects.create(name='one hundred and one hrivnyas', price=uah100 + 1) SimpleMoneyModel.objects.create(name='ninety nine hrivnyas', price=uah100 - 1) # Exact: qset = SimpleMoneyModel.objects.filter(price__exact=usd100) assert qset.count() == 1 qset = SimpleMoneyModel.objects.filter(price__exact=eur100) assert qset.count() == 1 qset = SimpleMoneyModel.objects.filter(price__exact=uah100) assert qset.count() == 1 # Less than: qset = SimpleMoneyModel.objects.filter(price__lt=usd100) assert qset.count() == 1 assert qset[0].price == usd100 - 1 qset = SimpleMoneyModel.objects.filter(price__lt=eur100) assert qset.count() == 1 assert qset[0].price == eur100 - 1 qset = SimpleMoneyModel.objects.filter(price__lt=uah100) assert qset.count() == 1 assert qset[0].price == uah100 - 1 # Greater than: qset = SimpleMoneyModel.objects.filter(price__gt=usd100) assert qset.count() == 1 assert qset[0].price == usd100 + 1 qset = SimpleMoneyModel.objects.filter(price__gt=eur100) assert qset.count() == 1 assert qset[0].price == eur100 + 1 qset = SimpleMoneyModel.objects.filter(price__gt=uah100) assert qset.count() == 1 assert qset[0].price == uah100 + 1 # Less than or equal: qset = SimpleMoneyModel.objects.filter(price__lte=usd100) assert qset.count() == 2 assertsamecurrency([ent.price for ent in qset], 'USD') for ent in qset: assert ent.price.amount <= 100 qset = SimpleMoneyModel.objects.filter(price__lte=eur100) assert qset.count() == 2 assertsamecurrency([ent.price for ent in qset], 'EUR') for ent in qset: assert ent.price.amount <= 100 qset = SimpleMoneyModel.objects.filter(price__lte=uah100) assert qset.count() == 2 assertsamecurrency([ent.price for ent in qset], 'UAH') for ent in qset: assert ent.price.amount <= 100 # Greater than or equal: qset = SimpleMoneyModel.objects.filter(price__gte=usd100) assert qset.count() == 2 assertsamecurrency([ent.price for ent in qset], 'USD') qset = SimpleMoneyModel.objects.filter(price__gte=eur100) assert qset.count() == 2 assertsamecurrency([ent.price for ent in qset], 'EUR') qset = SimpleMoneyModel.objects.filter(price__gte=uah100) assert qset.count() == 2 assertsamecurrency([ent.price for ent in qset], 'UAH')
class TestMoney(object): """ Tests of the Money class """ MONEY_CREATION = [ # Int values (Money(10, 'EUR'), Decimal(10), 'EUR'), (Money(-10, 'EUR'), Decimal(-10), 'EUR'), # Str values (Money('10', 'EUR'), Decimal(10), 'EUR'), (Money('-10', 'EUR'), Decimal(-10), 'EUR'), (Money(str('10.50'), str('EUR')), Decimal('10.50'), 'EUR'), (Money(str('-10.50'), str('EUR')), Decimal('-10.50'), 'EUR'), (Money('10.50', 'EUR'), Decimal('10.50'), 'EUR'), (Money('-10.50', 'EUR'), Decimal('-10.50'), 'EUR'), # Float values (Money(10.50, 'EUR'), Decimal('10.50'), 'EUR'), (Money(-10.50, 'EUR'), Decimal('-10.50'), 'EUR'), # Decimal values (Money(Decimal('10.50'), 'EUR'), Decimal('10.50'), 'EUR'), (Money(Decimal('-10.50'), 'EUR'), Decimal('-10.50'), 'EUR'), # Unspecified currency (Money(10), Decimal(10), settings.BASE_CURRENCY), # Unspecified amount (Money(currency='EUR'), 0, 'EUR'), # Internal type (Money(1, Currency(code='AAA', name='My Currency')), 1, 'AAA'), # Parsed value (Money('10 EUR'), 10, 'EUR'), ] @pytest.mark.parametrize('value,expected_amount,expected_currency', MONEY_CREATION) def test_creation(self, value, expected_amount, expected_currency): assert isinstance(value, Money) assert value.amount == expected_amount assert value.currency.code == expected_currency MONEY_CREATION_UNSUPPORTED = [ (lambda: Money('10 EUR', 'USD')), (lambda: Money('EUR 10 EUR')), ] @pytest.mark.parametrize('value', MONEY_CREATION_UNSUPPORTED) def test_invalid_creation(self, value): with pytest.raises(IncorrectMoneyInputError): value() def test_amount_attribute(self): value = Money(101, 'USD') assert value.amount == 101 def test_mutation_of_amount_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.amount = 1 MONEY_PRECISION = [(Money('10 EUR'), Decimal(10)), (Money('10.2 EUR'), Decimal('10.2')), (Money('10.225 EUR'), Decimal('10.23')), (Money('10.226 EUR'), Decimal('10.23')), (Money('10.2288 EUR'), Decimal('10.23'))] @pytest.mark.parametrize('value,expected', MONEY_PRECISION) def test_amount_rounded_attribute(self, value, expected): assert value.amount_rounded == expected def test_mutation_of_amount_rounded_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.amount_rounded = 1 def test_currency_attribute(self): value = Money(101, 'USD') assert value.currency == 'USD' def test_mutation_of_currency_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.currency = 'USD' MONEY_STRINGS = [ # Default currency: ( Money(' 123'), '123 {}'.format(settings.BASE_CURRENCY), ), ( Money('-123'), '-123 {}'.format(settings.BASE_CURRENCY), ), # Test a currency with decimals: ( Money('123', 'EUR'), '123 EUR', ), ( Money('-123', 'EUR'), '-123 EUR', ), ( Money('123.0000', 'EUR'), '123.0000 EUR', ), ( Money('-123.0000', 'EUR'), '-123.0000 EUR', ), ( Money('123.25', 'EUR'), '123.25 EUR', ), ( Money('-123.25', 'EUR'), '-123.25 EUR', ), # Test a currency that is normally written without decimals: ( Money('123', 'JPY'), '123 JPY', ), ( Money('-123', 'JPY'), '-123 JPY', ), ( Money('123.0000', 'JPY'), '123.0000 JPY', ), ( Money('-123.0000', 'JPY'), '-123.0000 JPY', ), ( Money('123.25', 'JPY'), '123.25 JPY', ), ( Money('-123.25', 'JPY'), '-123.25 JPY', ), ] @pytest.mark.parametrize('value,expected', MONEY_STRINGS) def test_str(self, value, expected): assert str(value) == expected @pytest.mark.parametrize('value,expected', MONEY_STRINGS) def test_repr(self, value, expected): assert repr(value) == expected MONEY_ARITHMETIC = [ # Casting (lambda: Money('100') + 0.5, Money('100.5')), (lambda: float(Money('100')), float(100)), (lambda: int(Money('100')), 100), # Addition (lambda: Money('100') + Money('100'), Money('200')), (lambda: Money('100') + Money('-100'), Money('0')), (lambda: Money('100') + 100, Money('200')), (lambda: Money('100') + -100, Money('0')), (lambda: Money('100') + Decimal('100'), Money('200')), (lambda: Money('100') + Decimal('-100'), Money('0')), # Subtraction (lambda: Money('100') - Money('100'), Money('0')), (lambda: Money('100') - Money('-100'), Money('200')), (lambda: Money('100') - 100, Money('0')), (lambda: Money('100') - -100, Money('200')), (lambda: Money('100') - Decimal('100'), Money('0')), (lambda: Money('100') - Decimal('-100'), Money('200')), # Multiplication (lambda: Money('100') * 4, Money('400')), (lambda: Money('100') * Decimal('4'), Money('400')), # Division (lambda: Money('100') / 4, Money('25')), (lambda: Money('100') / Decimal('4'), Money('25')), # Negation (lambda: -Money('100'), Money('-100')), (lambda: -Money('100.12', 'EUR'), Money('-100.12', 'EUR')), (lambda: +Money('100'), Money('100')), # Absolute value (lambda: abs(Money('-100')), Money('100')), (lambda: abs(Money('-100.12', 'EUR')), Money('100.12', 'EUR')), (lambda: abs(Money('0')), Money('0')), (lambda: abs(Money('100')), Money('100')), (lambda: abs(Money('100.12', 'EUR')), Money('100.12', 'EUR')), ] @pytest.mark.parametrize('value,expected', MONEY_ARITHMETIC) def test_arithmetic(self, value, expected): result = value() assert result == expected MONEY_ARITHMETIC_UNSUPPORTED = [ # Modulus (lambda: 4 % Money('100')), (lambda: Decimal('4') % Money('100')), (lambda: Money('100') % 4), (lambda: Money('100') % Decimal('4')), # Division: floor division (see future import above) (lambda: Money('100') // 4), (lambda: Money('100') // Decimal('4')), # Dividing a value by Money (lambda: 4 / Money('100')), (lambda: Decimal('4') / Money('100')), (lambda: Money('100') / Money('100')), # Multiplication of 2 Money objects (lambda: Money('100') * Money('100')), # Subtracting money from a digit (lambda: 100 - Money('100')), (lambda: -100 - Money('100')), (lambda: Decimal('100') - Money('100')), (lambda: Decimal('-100') - Money('100')), ] @pytest.mark.parametrize('value', MONEY_ARITHMETIC_UNSUPPORTED) def test_invalid_arithmetic(self, value): with pytest.raises(TypeError): value() MONEY_ARITHMETIC_MISMATCHED = [ # Mismatched currencies (lambda: Money('100', 'JPY') + Money('100', 'EUR')), (lambda: Money('100', 'JPY') - Money('100', 'EUR')), ] @pytest.mark.parametrize('value', MONEY_ARITHMETIC_MISMATCHED) def test_invalid_currency(self, value): with pytest.raises(CurrencyMismatch): value() MONEY_EQUALITY = [ # Bool (bool(Money('0')), False), (bool(Money('1')), True), (bool(Money('0', 'EUR')), False), (bool(Money('1', 'EUR')), True), (bool(Money('-1', 'EUR')), True), # Equality (Money('0') == Money('0'), True), (Money('100') == Money('100'), True), (Money('-100') == Money('-100'), True), (Money('100', 'EUR') == Money('100', 'EUR'), True), (Money('100.0', 'EUR') == Money('100', 'EUR'), True), # Mismatched currencies (Money('0', 'EUR') == Money('0', 'JPY'), False), (Money('100', 'JPY') == Money('100'), False), (Money('100', 'EUR') == Money('100', 'JPY'), False), (Money('100.0', 'EUR') == Money('100', 'JPY'), False), # Other types (Money('100.0', 'EUR') == Decimal('100'), False), (Money('100.0', 'EUR') == 100, False), (Decimal('100') == Money('100.0', 'EUR'), False), (100 == Money('100.0', 'EUR'), False), # Inequality (Money('0') != Money('0'), False), (Money('100') != Money('100'), False), (Money('-100') != Money('-100'), False), (Money('100', 'EUR') != Money('100', 'EUR'), False), (Money('100.0', 'EUR') != Money('100', 'EUR'), False), # Mismatched currencies (Money('0', 'EUR') != Money('0', 'JPY'), True), (Money('100', 'JPY') != Money('100'), True), (Money('100', 'EUR') != Money('100', 'JPY'), True), (Money('100.0', 'EUR') != Money('100', 'JPY'), True), # Other types (Money('100.0', 'EUR') != Decimal('100'), True), (Money('100.0', 'EUR') != 100, True), (Decimal('100') != Money('100.0', 'EUR'), True), (100 != Money('100.0', 'EUR'), True), # LT/GT (0 < Money('0'), False), (100 < Money('100'), False), (-100 < Money('-100'), False), (100 < Money('100', 'EUR'), False), (100.0 < Money('100', 'EUR'), False), (0 > Money('1'), False), (1 > Money('0'), True), (-101 > Money('-100'), False), (-100 > Money('-101'), True), (100 > Money('100.01', 'EUR'), False), (100.01 > Money('100', 'EUR'), True), (Money('0') < Money('0'), False), (Money('100') < Money('100'), False), (Money('-100') < Money('-100'), False), (Money('100', 'EUR') < Money('100', 'EUR'), False), (Money('100.0', 'EUR') < Money('100', 'EUR'), False), (Money('0') > Money('0'), False), (Money('100') > Money('100'), False), (Money('-100') > Money('-100'), False), (Money('100', 'EUR') > Money('100', 'EUR'), False), (Money('0') < Money('1'), True), (Money('1') < Money('0'), False), (Money('-101') < Money('-100'), True), (Money('-100') < Money('-101'), False), (Money('100', 'EUR') < Money('100.01', 'EUR'), True), (Money('100.01', 'EUR') < Money('100', 'EUR'), False), (Money('0') > Money('1'), False), (Money('1') > Money('0'), True), (Money('-101') > Money('-100'), False), (Money('-100') > Money('-101'), True), (Money('100', 'EUR') > Money('100.01', 'EUR'), False), (Money('100.01', 'EUR') > Money('100', 'EUR'), True), (Money('100.0', 'EUR') < Money('100', 'EUR'), False), (Money('100.0', 'EUR') > Money('100', 'EUR'), False), # GTE/LTE (Money('0') <= Money('0'), True), (Money('100') <= Money('100'), True), (Money('-100') <= Money('-100'), True), (Money('100', 'EUR') <= Money('100', 'EUR'), True), (Money('100.0', 'EUR') <= Money('100', 'EUR'), True), (Money('0') >= Money('0'), True), (Money('100') >= Money('100'), True), (Money('-100') >= Money('-100'), True), (Money('100', 'EUR') >= Money('100', 'EUR'), True), (Money('100.0', 'EUR') >= Money('100', 'EUR'), True), (Money('0') <= Money('1'), True), (Money('1') <= Money('0'), False), (Money('-101') <= Money('-100'), True), (Money('-100') <= Money('-101'), False), (Money('100', 'EUR') <= Money('100.01', 'EUR'), True), (Money('100.01', 'EUR') <= Money('100', 'EUR'), False), (Money('0') >= Money('1'), False), (Money('1') >= Money('0'), True), (Money('-101') >= Money('-100'), False), (Money('-100') >= Money('-101'), True), (Money('100', 'EUR') >= Money('100.01', 'EUR'), False), (Money('100.01', 'EUR') >= Money('100', 'EUR'), True), (Money('100.0', 'EUR') <= Money('100', 'EUR'), True), (Money('100.0', 'EUR') >= Money('100', 'EUR'), True), ] @pytest.mark.parametrize('value,expected', MONEY_EQUALITY) def test_equality(self, value, expected): assert value == expected def test_string_parse(self): value = Money.from_string('100.0 GBP') assert value.amount == Decimal('100.0') assert value.currency == 'GBP' assert value.currency == CURRENCIES['GBP'] def test_string_parse_default_currency(self): value = Money.from_string('100.35') assert value.amount == Decimal('100.35') assert value.currency == settings.BASE_CURRENCY assert value.currency == CURRENCIES[settings.BASE_CURRENCY] MONEY_ROUND = [ (lambda: Money('123.001', 'EUR').round(), Money('123', 'EUR')), (lambda: Money('123.005', 'EUR').round(), Money('123.01', 'EUR')), (lambda: Money('123.006', 'EUR').round(), Money('123.01', 'EUR')), (lambda: Money('123.001', 'EUR').round(3), Money('123.001', 'EUR')), (lambda: Money('123.005', 'EUR').round(3), Money('123.005', 'EUR')), (lambda: Money('123.006', 'EUR').round(3), Money('123.006', 'EUR')), ] @pytest.mark.parametrize('value,expected', MONEY_ROUND) def test_round(self, value, expected): assert value() == expected
def test_price_attribute(self): m = SimpleMoneyModel() m.price = Money(3, 'BGN') assert m.price == Money(3, 'BGN')
def test_price_attribute_in_constructor(self): m1 = SimpleMoneyModel(price=Money(100, 'USD')) m2 = SimpleMoneyModel(price=Money(200, 'JPY')) assert m1.price == Money(100, 'USD') assert m2.price == Money(200, 'JPY')
def test_price_attribute_update(self): m = SimpleMoneyModel(price=Money(200, 'JPY')) m.price = Money(300, 'USD') assert m.price == Money(300, 'USD')
def test_amount_attribute(self): value = Money(101, 'USD') assert value.amount == 101