# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import ast 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") 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)