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

285 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ast
from markupsafe import Markup
from odoo import Command
from odoo.tests.common import TransactionCase, BaseCase
from odoo.tools import mute_logger
from odoo.tools.safe_eval import safe_eval, const_eval, expr_eval
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
class TestSafeEval(BaseCase):
def test_const(self):
# NB: True and False are names in Python 2 not consts
expected = (1, {"a": {2.5}}, [None, u"foo"])
actual = const_eval('(1, {"a": {2.5}}, [None, u"foo"])')
self.assertEqual(actual, expected)
def test_expr(self):
# NB: True and False are names in Python 2 not consts
expected = 3 * 4
actual = expr_eval('3 * 4')
self.assertEqual(actual, expected)
def test_01_safe_eval(self):
""" Try a few common expressions to verify they work with safe_eval """
expected = (1, {"a": 9 * 2}, (True, False, None))
actual = safe_eval('(1, {"a": 9 * 2}, (True, False, None))')
self.assertEqual(actual, expected, "Simple python expressions are not working with safe_eval")
def test_02_literal_eval(self):
""" Try simple literal definition to verify it works with literal_eval """
expected = (1, {"a": 9}, (True, False, None))
actual = ast.literal_eval('(1, {"a": 9}, (True, False, None))')
self.assertEqual(actual, expected, "Simple python expressions are not working with literal_eval")
def test_03_literal_eval_arithmetic(self):
""" Try arithmetic expression in literal_eval to verify it does not work """
with self.assertRaises(ValueError):
ast.literal_eval('(1, {"a": 2*9}, (True, False, None))')
def test_04_literal_eval_forbidden(self):
""" Try forbidden expressions in literal_eval to verify they are not allowed """
with self.assertRaises(ValueError):
ast.literal_eval('{"a": True.__class__}')
@mute_logger('odoo.tools.safe_eval')
def test_05_safe_eval_forbiddon(self):
""" Try forbidden expressions in safe_eval to verify they are not allowed"""
# no forbidden builtin expression
with self.assertRaises(ValueError):
safe_eval('open("/etc/passwd","r")')
# no forbidden opcodes
with self.assertRaises(ValueError):
safe_eval("import odoo", mode="exec")
# no dunder
with self.assertRaises(NameError):
safe_eval("self.__name__", {'self': self}, mode="exec")
def test_06_safe_eval_format(self):
# string.format
self.assertEqual(safe_eval("'__{0}__'.format('Foo')"), '__Foo__')
self.assertEqual(safe_eval("'{0.__self__}'.format(abs)"), '{0.__self__}')
self.assertEqual(safe_eval("'{0.f_globals}'.format(abs)"), '{0.f_globals}')
# string.format_map
self.assertEqual(safe_eval("'__{foo}__'.format_map({'foo': 'Foo'})"), '__Foo__')
self.assertEqual(safe_eval("'{foo.__self__}'.format_map({'foo': abs})"), '{foo.__self__}')
self.assertEqual(safe_eval("'{foo.f_globals}'.format_map({'foo': abs})"), '{foo.f_globals}')
# Evaluation context for Markup asserts
c = {"Markup": Markup}
# Markup.format
self.assertEqual(safe_eval("Markup('__{0}__').format('Foo')", c), Markup('__Foo__'))
with self.assertRaisesRegex(ValueError, 'Access to forbidden name'):
safe_eval("Markup('{0.__self__}').format(abs)", c)
with self.assertRaisesRegex(ValueError, 'Access to forbidden name'):
safe_eval("Markup('{0.f_globals}').format(abs)", c)
# Markup.format_map
self.assertEqual(safe_eval("Markup('__{foo}__').format_map({'foo': 'Foo'})", c), Markup('__Foo__'))
self.assertEqual(safe_eval("Markup('{foo.__self__}').format_map({'foo': abs})", c), Markup('{foo.__self__}'))
self.assertEqual(safe_eval("Markup('{foo.f_globals}').format_map({'foo': abs})", c), Markup('{foo.f_globals}'))
def test_07_safe_eval_attribute_error_obj(self):
locals_dict = {}
try:
safe_eval("""
try:
dict.foo
except Exception as e:
action = {'args': e.args, 'obj': e.obj, 'name': e.name}
""", locals_dict=locals_dict, mode="exec", nocopy=True)
except ValueError as e:
# AttributeError.name, AttributeError.obj added in Python 3.10
# https://github.com/python/cpython/commit/37494b441aced0362d7edd2956ab3ea7801e60c8
self.assertIn("'AttributeError' object has no attribute 'obj'", e.args[0])
else:
exception = locals_dict.get('action')
self.assertEqual(exception['args'], ("type object 'dict' has no attribute 'foo'",))
self.assertIsNone(exception['name'])
self.assertIsNone(exception['obj'])
attribute_error = None
try:
raise AttributeError('Foo', name='Bar', obj=[])
except TypeError as e:
# AttributeError does not take keyword arguments before Python 3.10
# https://github.com/python/cpython/commit/37494b441aced0362d7edd2956ab3ea7801e60c8
# Error can be either, according to the Python version:
# - AttributeError does not take keyword arguments
# - AttributeError() takes no keyword arguments
self.assertIn("keyword arguments", e.args[0])
except AttributeError as e:
attribute_error = e
if attribute_error:
self.assertEqual(attribute_error.args, ('Foo',))
self.assertEqual(attribute_error.name, 'Bar')
self.assertIsNone(attribute_error.obj)
class TestParentStore(TransactionCase):
""" Verify that parent_store computation is done right """
def setUp(self):
super(TestParentStore, self).setUp()
# force res_partner_category.copy() to copy children
category = self.env['res.partner.category']
self.patch(category._fields['child_ids'], 'copy', True)
# setup categories
self.root = category.create({'name': 'Root category'})
self.cat0 = category.create({'name': 'Parent category', 'parent_id': self.root.id})
self.cat1 = category.create({'name': 'Child 1', 'parent_id': self.cat0.id})
self.cat2 = category.create({'name': 'Child 2', 'parent_id': self.cat0.id})
self.cat21 = category.create({'name': 'Child 2-1', 'parent_id': self.cat2.id})
def test_duplicate_parent(self):
""" Duplicate the parent category and verify that the children have been duplicated too """
new_cat0 = self.cat0.copy()
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
def test_duplicate_children_01(self):
""" Duplicate the children then reassign them to the new parent (1st method). """
new_cat1 = self.cat1.copy()
new_cat2 = self.cat2.copy()
new_cat0 = self.cat0.copy({'child_ids': []})
(new_cat1 + new_cat2).write({'parent_id': new_cat0.id})
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
def test_duplicate_children_02(self):
""" Duplicate the children then reassign them to the new parent (2nd method). """
new_cat1 = self.cat1.copy()
new_cat2 = self.cat2.copy()
new_cat0 = self.cat0.copy({'child_ids': [Command.set((new_cat1 + new_cat2).ids)]})
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
def test_duplicate_children_03(self):
""" Duplicate the children then reassign them to the new parent (3rd method). """
new_cat1 = self.cat1.copy()
new_cat2 = self.cat2.copy()
new_cat0 = self.cat0.copy({'child_ids': []})
new_cat0.write({'child_ids': [Command.link(new_cat1.id), Command.link(new_cat2.id)]})
new_struct = new_cat0.search([('parent_id', 'child_of', new_cat0.id)])
self.assertEqual(len(new_struct), 4, "After duplication, the new object must have the childs records")
old_struct = new_cat0.search([('parent_id', 'child_of', self.cat0.id)])
self.assertEqual(len(old_struct), 4, "After duplication, previous record must have old childs records only")
self.assertFalse(new_struct & old_struct, "After duplication, nodes should not be mixed")
class TestGroups(TransactionCase):
def test_res_groups_fullname_search(self):
all_groups = self.env['res.groups'].search([])
groups = all_groups.search([('full_name', 'like', 'Sale')])
self.assertItemsEqual(groups.ids, [g.id for g in all_groups if 'Sale' in g.full_name],
"did not match search for 'Sale'")
groups = all_groups.search([('full_name', 'like', 'Technical')])
self.assertItemsEqual(groups.ids, [g.id for g in all_groups if 'Technical' in g.full_name],
"did not match search for 'Technical'")
groups = all_groups.search([('full_name', 'like', 'Sales /')])
self.assertItemsEqual(groups.ids, [g.id for g in all_groups if 'Sales /' in g.full_name],
"did not match search for 'Sales /'")
groups = all_groups.search([('full_name', 'in', ['Administration / Access Rights','Contact Creation'])])
self.assertTrue(groups, "did not match search for 'Administration / Access Rights' and 'Contact Creation'")
def test_res_group_recursion(self):
# four groups with no cycle, check them all together
a = self.env['res.groups'].create({'name': 'A'})
b = self.env['res.groups'].create({'name': 'B'})
c = self.env['res.groups'].create({'name': 'G', 'implied_ids': [Command.set((a + b).ids)]})
d = self.env['res.groups'].create({'name': 'D', 'implied_ids': [Command.set(c.ids)]})
self.assertTrue((a + b + c + d)._check_m2m_recursion('implied_ids'))
# create a cycle and check
a.implied_ids = d
self.assertFalse(a._check_m2m_recursion('implied_ids'))
def test_res_group_copy(self):
a = self.env['res.groups'].with_context(lang='en_US').create({'name': 'A'})
b = a.copy()
self.assertFalse(a.name == b.name)
def test_apply_groups(self):
a = self.env['res.groups'].create({'name': 'A'})
b = self.env['res.groups'].create({'name': 'B'})
c = self.env['res.groups'].create({'name': 'C', 'implied_ids': [Command.set(a.ids)]})
# C already implies A, we want both B+C to imply A
(b + c)._apply_group(a)
self.assertIn(a, b.implied_ids)
self.assertIn(a, c.implied_ids)
def test_remove_groups(self):
u1 = self.env['res.users'].create({'login': 'u1', 'name': 'U1'})
u2 = self.env['res.users'].create({'login': 'u2', 'name': 'U2'})
default = self.env.ref('base.default_user')
portal = self.env.ref('base.group_portal')
p = self.env['res.users'].create({'login': 'p', 'name': 'P', 'groups_id': [Command.set([portal.id])]})
a = self.env['res.groups'].create({'name': 'A', 'users': [Command.set(u1.ids)]})
b = self.env['res.groups'].create({'name': 'B', 'users': [Command.set(u1.ids)]})
c = self.env['res.groups'].create({'name': 'C', 'implied_ids': [Command.set(a.ids)], 'users': [Command.set([p.id, u2.id, default.id])]})
d = self.env['res.groups'].create({'name': 'D', 'implied_ids': [Command.set(a.ids)], 'users': [Command.set([u2.id, default.id])]})
def assertUsersEqual(users, group):
self.assertEqual(
sorted([r.login for r in users]),
sorted([r.login for r in group.with_context(active_test=False).users])
)
# sanity checks
assertUsersEqual([u1, u2, p, default], a)
assertUsersEqual([u1], b)
assertUsersEqual([u2, p, default], c)
assertUsersEqual([u2, default], d)
# C already implies A, we want none of B+C to imply A
(b + c)._remove_group(a)
self.assertNotIn(a, b.implied_ids)
self.assertNotIn(a, c.implied_ids)
self.assertIn(a, d.implied_ids)
# - Since B didn't imply A, removing A from the implied groups of (B+C)
# should not remove user U1 from A, even though C implied A, since C does
# not have U1 as a user
# - P should be removed as was only added via inheritance to C
# - U2 should not be removed from A since it is implied via C but also via D
assertUsersEqual([u1, u2, default], a)
assertUsersEqual([u1], b)
assertUsersEqual([u2, p, default], c)
assertUsersEqual([u2, default], d)
# When adding the template user to a new group, it should add it to existing internal users
e = self.env['res.groups'].create({'name': 'E'})
default.write({'groups_id': [Command.link(e.id)]})
self.assertIn(u1, e.users)
self.assertIn(u2, e.users)
self.assertIn(default, e.with_context(active_test=False).users)
self.assertNotIn(p, e.users)