# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import datetime from decimal import ROUND_HALF_DOWN, Decimal from trytond import backend from trytond.modules.currency.ecb import ( RatesNotAvailableError, UnsupportedCurrencyError, get_rates) from trytond.pool import Pool from trytond.tests.test_tryton import ( ModuleTestCase, TestCase, with_transaction) from trytond.transaction import Transaction def create_currency(name): pool = Pool() Currency = pool.get('currency.currency') return Currency.create([{ 'name': name, 'symbol': name, 'code': name, }])[0] def add_currency_rate(currency, rate, date=datetime.date.min): pool = Pool() Rate = pool.get('currency.currency.rate') return Rate.create([{ 'currency': currency.id, 'rate': rate, 'date': date, }])[0] class CurrencyTestCase(ModuleTestCase): 'Test Currency module' module = 'currency' def get_currency(self, code): return self.currency.search([ ('code', '=', code), ], limit=1)[0] @with_transaction() def test_currencies(self): 'Create currencies' cu1 = create_currency('cu1') cu2 = create_currency('cu2') self.assertTrue(cu1) self.assertTrue(cu2) @with_transaction() def test_rate(self): 'Create rates' cu1 = create_currency('cu1') cu2 = create_currency('cu2') rate1 = add_currency_rate(cu1, Decimal("1.3")) rate2 = add_currency_rate(cu2, Decimal("1")) self.assertTrue(rate1) self.assertTrue(rate2) self.assertEqual(cu1.rate, Decimal("1.3")) @with_transaction() def test_rate_unicity(self): 'Rate unicity' pool = Pool() Rate = pool.get('currency.currency.rate') Date = pool.get('ir.date') today = Date.today() cu = create_currency('cu') Rate.create([{ 'rate': Decimal("1.3"), 'currency': cu.id, 'date': today, }]) self.assertRaises(Exception, Rate.create, { 'rate': Decimal("1.3"), 'currency': cu.id, 'date': today, }) @with_transaction() def test_round(self): "Test simple round" cu = create_currency('cu') cu.rounding = Decimal('0.001') cu.digits = 3 cu.save() rounded = cu.round(Decimal('1.2345678')) self.assertEqual(rounded, Decimal('1.235')) @with_transaction() def test_round_non_unity(self): "Test round with non unity" cu = create_currency('cu') cu.rounding = Decimal('0.02') cu.digits = 2 cu.save() rounded = cu.round(Decimal('1.2345')) self.assertEqual(rounded, Decimal('1.24')) @with_transaction() def test_round_big_number(self): "Test rounding big number" cu = create_currency('cu') rounded = cu.round(Decimal('1E50')) self.assertEqual(rounded, Decimal('1E50')) @with_transaction() def test_round_opposite(self): "Test the opposite rounding" cu = create_currency('cu') cu.save() rounded = cu.round(Decimal('1.235')) self.assertEqual(rounded, Decimal('1.24')) opposite_rounded = cu.round(Decimal('1.235'), opposite=True) self.assertEqual(opposite_rounded, Decimal('1.24')) @with_transaction() def test_round_opposite_HALF_DOWN(self): "Test the oposite rounding of ROUND_HALF_DOWN" cu = create_currency('cu') cu.save() rounded = cu.round(Decimal('1.235'), rounding=ROUND_HALF_DOWN) self.assertEqual(rounded, Decimal('1.23')) opposite_rounded = cu.round( Decimal('1.235'), rounding=ROUND_HALF_DOWN, opposite=True) self.assertEqual(opposite_rounded, Decimal('1.24')) @with_transaction() def test_is_zero(self): "Test is zero" cu = create_currency('cu') cu.rounding = Decimal('0.001') cu.digits = 3 cu.save() for value, result in [ (Decimal('0'), True), (Decimal('0.0002'), True), (Decimal('0.0009'), False), (Decimal('0.002'), False), ]: with self.subTest(value=value): self.assertEqual(cu.is_zero(value), result) with self.subTest(value=-value): self.assertEqual(cu.is_zero(-value), result) @with_transaction() def test_compute_simple(self): 'Simple conversion' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') cu2 = create_currency('cu2') add_currency_rate(cu1, Decimal("1.3")) add_currency_rate(cu2, Decimal("1")) amount = Decimal("10") expected = Decimal("13") converted_amount = Currency.compute( cu2, amount, cu1, True) self.assertEqual(converted_amount, expected) @with_transaction() def test_compute_nonfinite(self): 'Conversion with rounding on non-finite decimal representation' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') cu2 = create_currency('cu2') add_currency_rate(cu1, Decimal("1.3")) add_currency_rate(cu2, Decimal("1")) amount = Decimal("10") expected = Decimal("7.69") converted_amount = Currency.compute( cu1, amount, cu2, True) self.assertEqual(converted_amount, expected) @with_transaction() def test_compute_nonfinite_worounding(self): 'Same without rounding' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') cu2 = create_currency('cu2') add_currency_rate(cu1, Decimal("1.3")) add_currency_rate(cu2, Decimal("1")) amount = Decimal("10") expected = Decimal('7.692307692307692307692307692') converted_amount = Currency.compute( cu1, amount, cu2, False) self.assertEqual(converted_amount, expected) @with_transaction() def test_compute_same(self): 'Conversion to the same currency' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') add_currency_rate(cu1, Decimal("1.3")) amount = Decimal("10") converted_amount = Currency.compute( cu1, amount, cu1, True) self.assertEqual(converted_amount, amount) @with_transaction() def test_compute_zeroamount(self): 'Conversion with zero amount' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') cu2 = create_currency('cu2') add_currency_rate(cu1, Decimal("1.3")) add_currency_rate(cu2, Decimal("1")) expected = Decimal("0") converted_amount = Currency.compute( cu1, Decimal("0"), cu2, True) self.assertEqual(converted_amount, expected) @with_transaction() def test_compute_zerorate(self): 'Conversion with zero rate' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') cu2 = create_currency('cu2') add_currency_rate(cu2, Decimal('1')) amount = Decimal("10") self.assertRaises(Exception, Currency.compute, cu1, amount, cu2, True) self.assertRaises(Exception, Currency.compute, cu2, amount, cu1, True) @with_transaction() def test_compute_missingrate(self): 'Conversion with missing rate' pool = Pool() Currency = pool.get('currency.currency') cu1 = create_currency('cu1') cu3 = create_currency('cu3') add_currency_rate(cu1, Decimal("1.3")) amount = Decimal("10") self.assertRaises(Exception, Currency.compute, cu3, amount, cu1, True) self.assertRaises(Exception, Currency.compute, cu1, amount, cu3, True) @with_transaction() def test_compute_bothmissingrate(self): 'Conversion with both missing rate' pool = Pool() Currency = pool.get('currency.currency') cu3 = create_currency('cu3') cu4 = create_currency('cu4') amount = Decimal("10") self.assertRaises(Exception, Currency.compute, cu3, amount, cu4, True) @with_transaction() def test_delete_cascade(self): 'Test deletion of currency deletes rates' pool = Pool() Currency = pool.get('currency.currency') Rate = pool.get('currency.currency.rate') currencies = [create_currency('cu%s' % i) for i in range(3)] [add_currency_rate(c, Decimal('1')) for c in currencies] Currency.delete(currencies) rates = Rate.search([( 'currency', 'in', list(map(int, currencies)), )], 0, None, None) self.assertFalse(rates) @with_transaction() def test_currency_rate_sql(self): "Test currency rate SQL" pool = Pool() Currency = pool.get('currency.currency') Rate = pool.get('currency.currency.rate') transaction = Transaction() cursor = transaction.connection.cursor() date = datetime.date cu1 = create_currency('cu1') for date_, rate in [ (date(2017, 1, 1), Decimal(1)), (date(2017, 2, 1), Decimal(2)), (date(2017, 3, 1), Decimal(3))]: add_currency_rate(cu1, rate, date_) cu2 = create_currency('cu2') for date_, rate in [ (date(2017, 2, 1), Decimal(2)), (date(2017, 4, 1), Decimal(4))]: add_currency_rate(cu2, rate, date_) query = Currency.currency_rate_sql() if backend.name == 'sqlite': query.columns[-1].output_name += ( ' [%s]' % Rate.date.sql_type().base) cursor.execute(*query) data = set(cursor) result = { (cu1.id, Decimal(1), date(2017, 1, 1), date(2017, 2, 1)), (cu1.id, Decimal(2), date(2017, 2, 1), date(2017, 3, 1)), (cu1.id, Decimal(3), date(2017, 3, 1), None), (cu2.id, Decimal(2), date(2017, 2, 1), date(2017, 4, 1)), (cu2.id, Decimal(4), date(2017, 4, 1), None), } self.assertSetEqual(data, result) class ECBtestCase(TestCase): def test_rate_EUR_week_ago(self): "Fetch EUR rates a week ago" week_ago = datetime.date.today() - datetime.timedelta(days=7) rates = get_rates('EUR', week_ago) self.assertNotIn('EUR', rates) self.assertIn('USD', rates) def test_rate_USD_week_ago(self): "Fetch USD rates a week ago" week_ago = datetime.date.today() - datetime.timedelta(days=7) rates = get_rates('USD', week_ago) self.assertIn('EUR', rates) self.assertNotIn('USD', rates) def test_rate_EUR_on_weekend(self): "Fetch EUR rates on weekend" rates_fr = get_rates('EUR', datetime.date(2022, 9, 30)) rates_sa = get_rates('EUR', datetime.date(2022, 10, 2)) rates_su = get_rates('EUR', datetime.date(2022, 10, 2)) self.assertEqual(rates_sa, rates_fr) self.assertEqual(rates_su, rates_fr) def test_rate_USD_on_weekend(self): "Fetch USD rates on weekend" rates_fr = get_rates('USD', datetime.date(2022, 9, 30)) rates_sa = get_rates('USD', datetime.date(2022, 10, 2)) rates_su = get_rates('USD', datetime.date(2022, 10, 2)) self.assertEqual(rates_sa, rates_fr) self.assertEqual(rates_su, rates_fr) def test_rate_EUR_start_date(self): "Fetch EUR rates at start date" rates = get_rates('EUR', datetime.date(1999, 1, 4)) self.assertEqual(rates['USD'], Decimal('1.1789')) def test_rate_USD_start_date(self): "Fetch USD rates at start date" rates = get_rates('USD', datetime.date(1999, 1, 4)) self.assertEqual(rates['EUR'], Decimal('0.8482')) def test_rate_in_future(self): "Fetch rate in future raise an error" future = datetime.date.today() + datetime.timedelta(days=2) with self.assertRaises(RatesNotAvailableError): get_rates('USD', future) def test_rate_unsupported_currency(self): "Fetch rate for unsupported currency" with self.assertRaises(UnsupportedCurrencyError): get_rates('XXX', datetime.date(2022, 10, 3)) del ModuleTestCase