Files
odoo_source/odoo/addons/base/tests/test_misc.py
T
Xavier-Do e9b62b47dc [FIX] *: adapt tz for Ubuntu Noble
Some of the non canononical timezones are not present in Ubuntu Noble,
it would be a better practice to only use canonical timezones in data
and tests.

Note that this is not a real fix for all cases since the database that
ran on Ubuntu Jammy and are moved to an ubuntu Noble server will have
the issue with timezones already in database.

One of the possible fix would be to manage that during upgrades, but
this isn't a verry flexible solution since upgrade are meant to manage
chyange of version, not change of server. If an old 17.0 versions needs
to be moved to a Noble server, this won't work.

Another solution would be to install package like tzdata-legacy that may
keep the old timezones but it is not the only think since TAI-10 are
also in this package. This solution is not ideal because non canonical
timezone will still be shown in the dropdown. We would need to filter
them.

A last solution would be to add the support for those old timezones by
monkeypatching the lib. This way, only new timezones would be shown but
non canonical one won't crash when used. This is not ideal either
because we may need to keep this for a while. But in combination with
the upgrade solution, it may work proprely.

Part-of: odoo/odoo#160842
2024-04-07 10:03:48 +00:00

631 lines
27 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import datetime
from dateutil.relativedelta import relativedelta
import os.path
import pytz
from odoo.tools import (
config,
date_utils,
file_open,
file_path,
merge_sequences,
misc,
remove_accents,
validate_url,
)
from odoo.tests.common import TransactionCase, BaseCase
class TestCountingStream(BaseCase):
def test_empty_stream(self):
s = misc.CountingStream(iter([]))
self.assertEqual(s.index, -1)
self.assertIsNone(next(s, None))
self.assertEqual(s.index, 0)
def test_single(self):
s = misc.CountingStream(range(1))
self.assertEqual(s.index, -1)
self.assertEqual(next(s, None), 0)
self.assertIsNone(next(s, None))
self.assertEqual(s.index, 1)
def test_full(self):
s = misc.CountingStream(range(42))
for _ in s:
pass
self.assertEqual(s.index, 42)
def test_repeated(self):
""" Once the CountingStream has stopped iterating, the index should not
increase anymore (the internal state should not be allowed to change)
"""
s = misc.CountingStream(iter([]))
self.assertIsNone(next(s, None))
self.assertEqual(s.index, 0)
self.assertIsNone(next(s, None))
self.assertEqual(s.index, 0)
class TestMergeSequences(BaseCase):
def test_merge_sequences(self):
# base case
seq = merge_sequences(['A', 'B', 'C'])
self.assertEqual(seq, ['A', 'B', 'C'])
# 'Z' can be anywhere
seq = merge_sequences(['A', 'B', 'C'], ['Z'])
self.assertEqual(seq, ['A', 'B', 'C', 'Z'])
# 'Y' must precede 'C';
seq = merge_sequences(['A', 'B', 'C'], ['Y', 'C'])
self.assertEqual(seq, ['A', 'B', 'Y', 'C'])
# 'X' must follow 'A' and precede 'C'
seq = merge_sequences(['A', 'B', 'C'], ['A', 'X', 'C'])
self.assertEqual(seq, ['A', 'B', 'X', 'C'])
# all cases combined
seq = merge_sequences(
['A', 'B', 'C'],
['Z'], # 'Z' can be anywhere
['Y', 'C'], # 'Y' must precede 'C';
['A', 'X', 'Y'], # 'X' must follow 'A' and precede 'Y'
)
self.assertEqual(seq, ['A', 'B', 'X', 'Y', 'C', 'Z'])
class TestDateRangeFunction(BaseCase):
""" Test on date_range generator. """
def test_date_range_with_naive_datetimes(self):
""" Check date_range with naive datetimes. """
start = datetime.datetime(1985, 1, 1)
end = datetime.datetime(1986, 1, 1)
expected = [
datetime.datetime(1985, 1, 1, 0, 0),
datetime.datetime(1985, 2, 1, 0, 0),
datetime.datetime(1985, 3, 1, 0, 0),
datetime.datetime(1985, 4, 1, 0, 0),
datetime.datetime(1985, 5, 1, 0, 0),
datetime.datetime(1985, 6, 1, 0, 0),
datetime.datetime(1985, 7, 1, 0, 0),
datetime.datetime(1985, 8, 1, 0, 0),
datetime.datetime(1985, 9, 1, 0, 0),
datetime.datetime(1985, 10, 1, 0, 0),
datetime.datetime(1985, 11, 1, 0, 0),
datetime.datetime(1985, 12, 1, 0, 0),
datetime.datetime(1986, 1, 1, 0, 0)
]
dates = [date for date in date_utils.date_range(start, end)]
self.assertEqual(dates, expected)
def test_date_range_with_date(self):
""" Check date_range with naive datetimes. """
start = datetime.date(1985, 1, 1)
end = datetime.date(1986, 1, 1)
expected = [
datetime.date(1985, 1, 1),
datetime.date(1985, 2, 1),
datetime.date(1985, 3, 1),
datetime.date(1985, 4, 1),
datetime.date(1985, 5, 1),
datetime.date(1985, 6, 1),
datetime.date(1985, 7, 1),
datetime.date(1985, 8, 1),
datetime.date(1985, 9, 1),
datetime.date(1985, 10, 1),
datetime.date(1985, 11, 1),
datetime.date(1985, 12, 1),
datetime.date(1986, 1, 1),
]
self.assertEqual(list(date_utils.date_range(start, end)), expected)
def test_date_range_with_timezone_aware_datetimes_other_than_utc(self):
""" Check date_range with timezone-aware datetimes other than UTC."""
timezone = pytz.timezone('Europe/Brussels')
start = datetime.datetime(1985, 1, 1)
end = datetime.datetime(1986, 1, 1)
start = timezone.localize(start)
end = timezone.localize(end)
expected = [datetime.datetime(1985, 1, 1, 0, 0),
datetime.datetime(1985, 2, 1, 0, 0),
datetime.datetime(1985, 3, 1, 0, 0),
datetime.datetime(1985, 4, 1, 0, 0),
datetime.datetime(1985, 5, 1, 0, 0),
datetime.datetime(1985, 6, 1, 0, 0),
datetime.datetime(1985, 7, 1, 0, 0),
datetime.datetime(1985, 8, 1, 0, 0),
datetime.datetime(1985, 9, 1, 0, 0),
datetime.datetime(1985, 10, 1, 0, 0),
datetime.datetime(1985, 11, 1, 0, 0),
datetime.datetime(1985, 12, 1, 0, 0),
datetime.datetime(1986, 1, 1, 0, 0)]
expected = [timezone.localize(e) for e in expected]
dates = [date for date in date_utils.date_range(start, end)]
self.assertEqual(expected, dates)
def test_date_range_with_mismatching_zones(self):
""" Check date_range with mismatching zone should raise an exception."""
start_timezone = pytz.timezone('Europe/Brussels')
end_timezone = pytz.timezone('America/Recife')
start = datetime.datetime(1985, 1, 1)
end = datetime.datetime(1986, 1, 1)
start = start_timezone.localize(start)
end = end_timezone.localize(end)
with self.assertRaises(ValueError):
dates = [date for date in date_utils.date_range(start, end)]
def test_date_range_with_inconsistent_datetimes(self):
""" Check date_range with a timezone-aware datetime and a naive one."""
context_timezone = pytz.timezone('Europe/Brussels')
start = datetime.datetime(1985, 1, 1)
end = datetime.datetime(1986, 1, 1)
end = context_timezone.localize(end)
with self.assertRaises(ValueError):
dates = [date for date in date_utils.date_range(start, end)]
def test_date_range_with_hour(self):
""" Test date range with hour and naive datetime."""
start = datetime.datetime(2018, 3, 25)
end = datetime.datetime(2018, 3, 26)
step = relativedelta(hours=1)
expected = [
datetime.datetime(2018, 3, 25, 0, 0),
datetime.datetime(2018, 3, 25, 1, 0),
datetime.datetime(2018, 3, 25, 2, 0),
datetime.datetime(2018, 3, 25, 3, 0),
datetime.datetime(2018, 3, 25, 4, 0),
datetime.datetime(2018, 3, 25, 5, 0),
datetime.datetime(2018, 3, 25, 6, 0),
datetime.datetime(2018, 3, 25, 7, 0),
datetime.datetime(2018, 3, 25, 8, 0),
datetime.datetime(2018, 3, 25, 9, 0),
datetime.datetime(2018, 3, 25, 10, 0),
datetime.datetime(2018, 3, 25, 11, 0),
datetime.datetime(2018, 3, 25, 12, 0),
datetime.datetime(2018, 3, 25, 13, 0),
datetime.datetime(2018, 3, 25, 14, 0),
datetime.datetime(2018, 3, 25, 15, 0),
datetime.datetime(2018, 3, 25, 16, 0),
datetime.datetime(2018, 3, 25, 17, 0),
datetime.datetime(2018, 3, 25, 18, 0),
datetime.datetime(2018, 3, 25, 19, 0),
datetime.datetime(2018, 3, 25, 20, 0),
datetime.datetime(2018, 3, 25, 21, 0),
datetime.datetime(2018, 3, 25, 22, 0),
datetime.datetime(2018, 3, 25, 23, 0),
datetime.datetime(2018, 3, 26, 0, 0)
]
dates = [date for date in date_utils.date_range(start, end, step)]
self.assertEqual(dates, expected)
class TestFormatLangDate(TransactionCase):
def test_00_accepted_types(self):
self.env.user.tz = 'Europe/Brussels'
datetime_str = '2017-01-31 12:00:00'
date_datetime = datetime.datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
date_date = date_datetime.date()
date_str = '2017-01-31'
time_part = datetime.time(16, 30, 22)
t_medium = 'h:mm:ss a'
medium = f'MMM d, YYYY, {t_medium}'
self.assertEqual(misc.format_date(self.env, date_datetime), '01/31/2017')
self.assertEqual(misc.format_date(self.env, date_date), '01/31/2017')
self.assertEqual(misc.format_date(self.env, date_str), '01/31/2017')
self.assertEqual(misc.format_date(self.env, ''), '')
self.assertEqual(misc.format_date(self.env, False), '')
self.assertEqual(misc.format_date(self.env, None), '')
self.assertEqual(misc.format_datetime(self.env, date_datetime, dt_format=medium), 'Jan 31, 2017, 1:00:00 PM')
self.assertEqual(misc.format_datetime(self.env, datetime_str, dt_format=medium), 'Jan 31, 2017, 1:00:00 PM')
self.assertEqual(misc.format_datetime(self.env, '', dt_format=medium), '')
self.assertEqual(misc.format_datetime(self.env, False, dt_format=medium), '')
self.assertEqual(misc.format_datetime(self.env, None, dt_format=medium), '')
self.assertEqual(misc.format_time(self.env, time_part, time_format=t_medium), '4:30:22 PM')
self.assertEqual(misc.format_time(self.env, '', time_format=t_medium), '')
self.assertEqual(misc.format_time(self.env, False, time_format=t_medium), '')
self.assertEqual(misc.format_time(self.env, None, time_format=t_medium), '')
def test_01_code_and_format(self):
date_str = '2017-01-31'
lang = self.env['res.lang']
# Activate French and Simplified Chinese (test with non-ASCII characters)
lang._activate_lang('fr_FR')
lang._activate_lang('zh_CN')
# -- test `date`
# Change a single parameter
self.assertEqual(misc.format_date(lang.with_context(lang='fr_FR').env, date_str), '31/01/2017')
self.assertEqual(misc.format_date(lang.env, date_str, lang_code='fr_FR'), '31/01/2017')
self.assertEqual(misc.format_date(lang.env, date_str, date_format='MMM d, y'), 'Jan 31, 2017')
# Change 2 parameters
self.assertEqual(misc.format_date(lang.with_context(lang='zh_CN').env, date_str, lang_code='fr_FR'), '31/01/2017')
self.assertEqual(misc.format_date(lang.with_context(lang='zh_CN').env, date_str, date_format='MMM d, y'), u'1\u6708 31, 2017')
self.assertEqual(misc.format_date(lang.env, date_str, lang_code='fr_FR', date_format='MMM d, y'), 'janv. 31, 2017')
# Change 3 parameters
self.assertEqual(misc.format_date(lang.with_context(lang='zh_CN').env, date_str, lang_code='en_US', date_format='MMM d, y'), 'Jan 31, 2017')
# -- test `datetime`
datetime_str = '2017-01-31 10:33:00'
# Change languages and timezones
datetime_us_str = misc.format_datetime(lang.with_context(lang='en_US').env, datetime_str, tz='Europe/Brussels')
self.assertNotEqual(misc.format_datetime(lang.with_context(lang='fr_FR').env, datetime_str, tz='Europe/Brussels'), datetime_us_str)
self.assertNotEqual(misc.format_datetime(lang.with_context(lang='zh_CN').env, datetime_str, tz='America/New_York'), datetime_us_str)
# Change language, timezone and format
self.assertEqual(misc.format_datetime(lang.with_context(lang='fr_FR').env, datetime_str, tz='America/New_York', dt_format='dd/MM/YYYY HH:mm'), '31/01/2017 05:33')
self.assertEqual(misc.format_datetime(lang.with_context(lang='en_US').env, datetime_str, tz='Europe/Brussels', dt_format='MMM d, y'), 'Jan 31, 2017')
# Check given `lang_code` overwites context lang
fmt_fr = 'dd MMMM YYYY à HH:mm:ss Z'
fmt_us = "MMMM dd, YYYY 'at' hh:mm:ss a Z"
self.assertEqual(misc.format_datetime(lang.env, datetime_str, tz='Europe/Brussels', dt_format=fmt_fr, lang_code='fr_FR'), '31 janvier 2017 à 11:33:00 +0100')
self.assertEqual(misc.format_datetime(lang.with_context(lang='zh_CN').env, datetime_str, tz='Europe/Brussels', dt_format=fmt_us, lang_code='en_US'), 'January 31, 2017 at 11:33:00 AM +0100')
# -- test `time`
time_part = datetime.time(16, 30, 22)
time_part_tz = datetime.time(16, 30, 22, tzinfo=pytz.timezone('America/New_York')) # 4:30 PM timezoned
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='HH:mm:ss'), '16:30:22')
self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format="ah:m:ss"), '\u4e0b\u53484:30:22')
# Check format in different languages
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='HH:mm'), '16:30')
self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format='ah:mm'), '\u4e0b\u53484:30')
# Check timezoned time part
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part_tz, time_format='HH:mm:ss Z'), '16:30:22 -0504')
self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part_tz, time_format='zzzz ah:mm:ss'), '\u5317\u7f8e\u4e1c\u90e8\u6807\u51c6\u65f6\u95f4\u0020\u4e0b\u53484:30:22')
#Check timezone conversion in format_time
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, datetime_str, 'Europe/Brussels', time_format='HH:mm:ss Z'), '11:33:00 +0100')
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, datetime_str, 'America/New_York', time_format='HH:mm:ss Z'), '05:33:00 -0500')
# Check given `lang_code` overwites context lang
self.assertEqual(misc.format_time(lang.with_context(lang='fr_FR').env, time_part, time_format='ah:mm', lang_code='zh_CN'), '\u4e0b\u53484:30')
self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format='ah:mm', lang_code='fr_FR'), 'PM4:30')
def test_02_tz(self):
self.env.user.tz = 'Europe/Brussels'
datetime_str = '2016-12-31 23:55:00'
date_datetime = datetime.datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
# While London is still in 2016, Brussels is already in 2017
self.assertEqual(misc.format_date(self.env, date_datetime), '01/01/2017')
# Force London timezone
date_datetime = date_datetime.replace(tzinfo=pytz.UTC)
self.assertEqual(misc.format_date(self.env, date_datetime), '12/31/2016', "User's tz must be ignored when tz is specifed in datetime object")
class TestCallbacks(BaseCase):
def test_callback(self):
log = []
callbacks = misc.Callbacks()
# add foo
def foo():
log.append("foo")
callbacks.add(foo)
# add bar
@callbacks.add
def bar():
log.append("bar")
# add foo again
callbacks.add(foo)
# this should call foo(), bar(), foo()
callbacks.run()
self.assertEqual(log, ["foo", "bar", "foo"])
# this should do nothing
callbacks.run()
self.assertEqual(log, ["foo", "bar", "foo"])
def test_aggregate(self):
log = []
callbacks = misc.Callbacks()
# register foo once
@callbacks.add
def foo():
log.append(callbacks.data["foo"])
# aggregate data
callbacks.data.setdefault("foo", []).append(1)
callbacks.data.setdefault("foo", []).append(2)
callbacks.data.setdefault("foo", []).append(3)
# foo() is called once
callbacks.run()
self.assertEqual(log, [[1, 2, 3]])
self.assertFalse(callbacks.data)
callbacks.run()
self.assertEqual(log, [[1, 2, 3]])
def test_reentrant(self):
log = []
callbacks = misc.Callbacks()
# register foo that runs callbacks
@callbacks.add
def foo():
log.append("foo1")
callbacks.run()
log.append("foo2")
@callbacks.add
def bar():
log.append("bar")
# both foo() and bar() are called once
callbacks.run()
self.assertEqual(log, ["foo1", "bar", "foo2"])
callbacks.run()
self.assertEqual(log, ["foo1", "bar", "foo2"])
class TestRemoveAccents(BaseCase):
def test_empty_string(self):
self.assertEqual(remove_accents(False), False)
self.assertEqual(remove_accents(''), '')
self.assertEqual(remove_accents(None), None)
def test_latin(self):
self.assertEqual(remove_accents('Niño Hernández'), 'Nino Hernandez')
self.assertEqual(remove_accents('Anaïs Clémence'), 'Anais Clemence')
def test_non_latin(self):
self.assertEqual(remove_accents('العربية'), 'العربية')
self.assertEqual(remove_accents('русский алфавит'), 'русскии алфавит')
class TestAddonsFileAccess(BaseCase):
def assertCannotAccess(self, path, ExceptionType=FileNotFoundError, filter_ext=None):
with self.assertRaises(ExceptionType):
file_path(path, filter_ext=filter_ext)
def assertCanRead(self, path, needle='', mode='r', filter_ext=None):
with file_open(path, mode, filter_ext) as f:
self.assertIn(needle, f.read())
def assertCannotRead(self, path, ExceptionType=FileNotFoundError, filter_ext=None):
with self.assertRaises(ExceptionType):
file_open(path, filter_ext=filter_ext)
def test_file_path(self):
# absolute path
self.assertEqual(__file__, file_path(__file__))
self.assertEqual(__file__, file_path(__file__, filter_ext=None)) # means "no filter" too
self.assertEqual(__file__, file_path(__file__, filter_ext=('.py',)))
# directory target is ok
self.assertEqual(os.path.dirname(__file__), file_path(os.path.join(__file__, '..')))
# relative path
relpath = os.path.join(*(__file__.split(os.sep)[-3:])) # 'base/tests/test_misc.py'
self.assertEqual(__file__, file_path(relpath))
self.assertEqual(__file__, file_path(relpath, filter_ext=('.py',)))
# leading 'addons/' is ignored if present
self.assertTrue(file_path("addons/web/__init__.py"))
relpath = os.path.join('addons', relpath) # 'addons/base/tests/test_misc.py'
self.assertEqual(__file__, file_path(relpath))
# files in root_path are allowed
self.assertTrue(file_path('tools/misc.py'))
# errors when outside addons_paths
self.assertCannotAccess('/doesnt/exist')
self.assertCannotAccess('/tmp')
self.assertCannotAccess('../../../../../../../../../tmp')
self.assertCannotAccess(os.path.join(__file__, '../../../../../'))
# data_dir is forbidden
self.assertCannotAccess(config['data_dir'])
# errors for illegal extensions
self.assertCannotAccess(__file__, ValueError, filter_ext=('.png',))
# file doesnt exist but has wrong extension
self.assertCannotAccess(__file__.replace('.py', '.foo'), ValueError, filter_ext=('.png',))
def test_file_open(self):
# The needle includes UTF8 so we test reading non-ASCII files at the same time.
# This depends on the system locale and is harder to unit test, but if you manage to run the
# test with a non-UTF8 locale (`LC_ALL=fr_FR.iso8859-1 python3...`) it should not crash ;-)
test_needle = "A needle with non-ascii bytes: ♥"
# absolute path
self.assertCanRead(__file__, test_needle)
self.assertCanRead(__file__, test_needle.encode(), mode='rb')
self.assertCanRead(__file__, test_needle.encode(), mode='rb', filter_ext=('.py',))
# directory target *is* an error
with self.assertRaises(FileNotFoundError):
file_open(os.path.join(__file__, '..'))
# relative path
relpath = os.path.join(*(__file__.split(os.sep)[-3:])) # 'base/tests/test_misc.py'
self.assertCanRead(relpath, test_needle)
self.assertCanRead(relpath, test_needle.encode(), mode='rb')
self.assertCanRead(relpath, test_needle.encode(), mode='rb', filter_ext=('.py',))
# leading 'addons/' is ignored if present
self.assertCanRead("addons/web/__init__.py", "import")
relpath = os.path.join('addons', relpath) # 'addons/base/tests/test_misc.py'
self.assertCanRead(relpath, test_needle)
# files in root_path are allowed
self.assertCanRead('tools/misc.py')
# errors when outside addons_paths
self.assertCannotRead('/doesnt/exist')
self.assertCannotRead('')
self.assertCannotRead('/tmp')
self.assertCannotRead('../../../../../../../../../tmp')
self.assertCannotRead(os.path.join(__file__, '../../../../../'))
# data_dir is forbidden
self.assertCannotRead(config['data_dir'])
# errors for illegal extensions
self.assertCannotRead(__file__, ValueError, filter_ext=('.png',))
# file doesnt exist but has wrong extension
self.assertCannotRead(__file__.replace('.py', '.foo'), ValueError, filter_ext=('.png',))
class TestDictTools(BaseCase):
def test_readonly_dict(self):
d = misc.ReadonlyDict({'foo': 'bar'})
with self.assertRaises(TypeError):
d['baz'] = 'xyz'
with self.assertRaises(AttributeError):
d.update({'baz': 'xyz'})
with self.assertRaises(TypeError):
dict.update(d, {'baz': 'xyz'})
class TestFormatLang(TransactionCase):
def test_value_and_digits(self):
self.assertEqual(misc.formatLang(self.env, 100.23, digits=1), '100.2')
self.assertEqual(misc.formatLang(self.env, 100.23, digits=3), '100.230')
self.assertEqual(misc.formatLang(self.env, ''), '', 'If value is an empty string, it should return an empty string (not 0)')
self.assertEqual(misc.formatLang(self.env, 100), '100.00', 'If digits is None (default value), it should default to 2')
# Default rounding is 'HALF_EVEN'
self.assertEqual(misc.formatLang(self.env, 100.205), '100.20')
self.assertEqual(misc.formatLang(self.env, 100.215), '100.22')
def test_grouping(self):
self.env["res.lang"].create({
"name": "formatLang Lang",
"code": "fLT",
"grouping": "[3,2,-1]",
"decimal_point": "!",
"thousands_sep": "?",
})
self.env['res.lang']._activate_lang('fLT')
self.assertEqual(misc.formatLang(self.env['res.lang'].with_context(lang='fLT').env, 1000000000, grouping=True), '10000?00?000!00')
self.assertEqual(misc.formatLang(self.env['res.lang'].with_context(lang='fLT').env, 1000000000, grouping=False), '1000000000.00')
def test_decimal_precision(self):
decimal_precision = self.env['decimal.precision'].create({
'name': 'formatLang Decimal Precision',
'digits': 3, # We want .001 decimals to make sure the decimal precision parameter 'dp' is chosen.
})
self.assertEqual(misc.formatLang(self.env, 100, dp=decimal_precision.name), '100.000')
def test_currency_object(self):
currency_object = self.env['res.currency'].create({
'name': 'formatLang Currency',
'symbol': 'fL',
'rounding': 0.1, # We want .1 decimals to make sure 'currency_obj' is chosen.
'position': 'after',
})
self.assertEqual(misc.formatLang(self.env, 100, currency_obj=currency_object), '100.0%sfL' % u'\N{NO-BREAK SPACE}')
currency_object.write({'position': 'before'})
self.assertEqual(misc.formatLang(self.env, 100, currency_obj=currency_object), 'fL%s100.0' % u'\N{NO-BREAK SPACE}')
def test_decimal_precision_and_currency_object(self):
decimal_precision = self.env['decimal.precision'].create({
'name': 'formatLang Decimal Precision',
'digits': 3,
})
currency_object = self.env['res.currency'].create({
'name': 'formatLang Currency',
'symbol': 'fL',
'rounding': 0.1,
'position': 'after',
})
# If we have a 'dp' and 'currency_obj', we use the decimal precision of 'dp' and the format of 'currency_obj'.
self.assertEqual(misc.formatLang(self.env, 100, dp=decimal_precision.name, currency_obj=currency_object), '100.000%sfL' % u'\N{NO-BREAK SPACE}')
def test_rounding_method(self):
self.assertEqual(misc.formatLang(self.env, 100.205), '100.20') # Default is 'HALF-EVEN'
self.assertEqual(misc.formatLang(self.env, 100.215), '100.22') # Default is 'HALF-EVEN'
self.assertEqual(misc.formatLang(self.env, 100.205, rounding_method='HALF-UP'), '100.21')
self.assertEqual(misc.formatLang(self.env, 100.215, rounding_method='HALF-UP'), '100.22')
self.assertEqual(misc.formatLang(self.env, 100.205, rounding_method='HALF-DOWN'), '100.20')
self.assertEqual(misc.formatLang(self.env, 100.215, rounding_method='HALF-DOWN'), '100.21')
def test_rounding_unit(self):
self.assertEqual(misc.formatLang(self.env, 1000000.00), '1,000,000.00')
self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit='units'), '1,000,000')
self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit='thousands'), '1,000')
self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit='lakhs'), '10')
self.assertEqual(misc.formatLang(self.env, 1000000.00, rounding_unit="millions"), '1')
def test_rounding_method_and_rounding_unit(self):
self.assertEqual(misc.formatLang(self.env, 1822060000, rounding_method='HALF-UP', rounding_unit='lakhs'), '18,221')
self.assertEqual(misc.formatLang(self.env, 1822050000, rounding_method='HALF-UP', rounding_unit='lakhs'), '18,221')
self.assertEqual(misc.formatLang(self.env, 1822049900, rounding_method='HALF-UP', rounding_unit='lakhs'), '18,220')
class TestUrlValidate(BaseCase):
def test_url_validate(self):
for case, truth in [
# full URLs should be preserved
('http://example.com', 'http://example.com'),
('http://example.com/index.html', 'http://example.com/index.html'),
('http://example.com?debug=1', 'http://example.com?debug=1'),
('http://example.com#h3', 'http://example.com#h3'),
# URLs with a domain should get a http scheme
('example.com', 'http://example.com'),
('example.com/index.html', 'http://example.com/index.html'),
('example.com?debug=1', 'http://example.com?debug=1'),
('example.com#h3', 'http://example.com#h3'),
]:
with self.subTest(case=case):
self.assertEqual(validate_url(case), truth)
# broken cases, do we really want that?
self.assertEqual(validate_url('/index.html'), 'http:///index.html')
self.assertEqual(validate_url('?debug=1'), 'http://?debug=1')
self.assertEqual(validate_url('#model=project.task&id=3603607'), 'http://#model=project.task&id=3603607')