odoo_17.0.1/odoo/addons/base/tests/test_misc.py

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')