diff --git a/fusion_plating/fusion_plating/tests/__init__.py b/fusion_plating/fusion_plating/tests/__init__.py index 9da38da0..44ea4108 100644 --- a/fusion_plating/fusion_plating/tests/__init__.py +++ b/fusion_plating/fusion_plating/tests/__init__.py @@ -3,3 +3,4 @@ from . import test_fp_work_centre from . import test_fp_job_state_machine from . import test_fp_job_step_state_machine from . import test_simple_recipe_flatten +from . import test_role_groups diff --git a/fusion_plating/fusion_plating/tests/test_role_groups.py b/fusion_plating/fusion_plating/tests/test_role_groups.py new file mode 100644 index 00000000..a7ecfd8c --- /dev/null +++ b/fusion_plating/fusion_plating/tests/test_role_groups.py @@ -0,0 +1,104 @@ +from odoo.tests.common import TransactionCase, tagged + + +@tagged('-at_install', 'post_install', 'fp_perms') +class TestRoleGroupsStructure(TransactionCase): + """Verify the 8 new roles exist with correct implied_ids chains. + + Part of Phase 1 permissions overhaul. See: + docs/superpowers/specs/2026-05-23-permissions-overhaul-design.md + """ + + def test_all_seven_groups_exist(self): + """The 7 new res.groups records must all be defined. (The 8th role 'No' + is implicit — absence of any plating group.)""" + xmlids = { + 'group_fp_technician', 'group_fp_sales_rep', + 'group_fp_shop_manager_v2', 'group_fp_sales_manager', + 'group_fp_manager', 'group_fp_quality_manager', 'group_fp_owner', + } + for xmlid in xmlids: + grp = self.env.ref(f'fusion_plating.{xmlid}', raise_if_not_found=False) + self.assertTrue(grp, f'Group {xmlid} not found') + + def test_owner_implies_quality_manager(self): + owner = self.env.ref('fusion_plating.group_fp_owner') + qm = self.env.ref('fusion_plating.group_fp_quality_manager') + self.assertIn(qm, owner.implied_ids) + + def test_owner_implies_system(self): + owner = self.env.ref('fusion_plating.group_fp_owner') + system = self.env.ref('base.group_system') + self.assertIn(system, owner.trans_implied_ids, + 'Owner must transitively imply base.group_system') + + def test_manager_implies_both_branches(self): + """Manager is the diamond apex — must imply both Shop Manager and Sales Manager.""" + mgr = self.env.ref('fusion_plating.group_fp_manager') + sm = self.env.ref('fusion_plating.group_fp_shop_manager_v2') + sales_mgr = self.env.ref('fusion_plating.group_fp_sales_manager') + self.assertIn(sm, mgr.implied_ids, 'Manager must imply Shop Manager (diamond)') + self.assertIn(sales_mgr, mgr.implied_ids, 'Manager must imply Sales Manager (diamond)') + + def test_technician_does_not_imply_sales_rep(self): + """Sales and Shop branches must remain orthogonal at the leaf.""" + tech = self.env.ref('fusion_plating.group_fp_technician') + sales_rep = self.env.ref('fusion_plating.group_fp_sales_rep') + self.assertNotIn(sales_rep, tech.trans_implied_ids, + 'Technician must NOT see Sales Rep menus') + + def test_sales_rep_does_not_imply_technician(self): + sales_rep = self.env.ref('fusion_plating.group_fp_sales_rep') + tech = self.env.ref('fusion_plating.group_fp_technician') + self.assertNotIn(tech, sales_rep.trans_implied_ids, + 'Sales Rep must NOT see Workstation') + + def test_owner_auto_assigned_to_uid_1_and_2(self): + owner = self.env.ref('fusion_plating.group_fp_owner') + user_ids = owner.user_ids.ids + self.assertIn(1, user_ids, 'Owner must include uid 1 (__system__)') + self.assertIn(2, user_ids, 'Owner must include uid 2 (admin)') + + def test_sequence_numbers_are_unique(self): + seqs = [ + self.env.ref(f'fusion_plating.{x}').sequence + for x in ('group_fp_technician', 'group_fp_sales_rep', + 'group_fp_shop_manager_v2', 'group_fp_sales_manager', + 'group_fp_manager', 'group_fp_quality_manager', 'group_fp_owner') + ] + self.assertEqual(len(seqs), len(set(seqs)), + f'All sequence numbers must be unique, got {seqs}') + + def test_new_groups_imply_old_for_backward_compat(self): + """During the 30-day rollback window, new groups must trigger old ACLs.""" + tech = self.env.ref('fusion_plating.group_fp_technician') + old_op = self.env.ref('fusion_plating.group_fusion_plating_operator') + self.assertIn(old_op, tech.trans_implied_ids) + + mgr = self.env.ref('fusion_plating.group_fp_manager') + old_mgr = self.env.ref('fusion_plating.group_fusion_plating_manager') + self.assertIn(old_mgr, mgr.trans_implied_ids) + + def test_owner_implies_all_old_groups_via_cross_module_chain(self): + """Owner must transitively reach every old group (admin, manager, supervisor, + operator, estimator, receiving, accounting, cgp_officer, cgp_designated_official) + via the implication chain spread across fusion_plating + 4 downstream module + security files.""" + owner = self.env.ref('fusion_plating.group_fp_owner') + expected_old = [ + 'fusion_plating.group_fusion_plating_admin', + 'fusion_plating.group_fusion_plating_manager', + 'fusion_plating.group_fusion_plating_supervisor', + 'fusion_plating.group_fusion_plating_operator', + 'fusion_plating_configurator.group_fp_estimator', + 'fusion_plating_receiving.group_fp_receiving', + 'fusion_plating_invoicing.group_fp_accounting', + 'fusion_plating_cgp.group_fusion_plating_cgp_officer', + 'fusion_plating_cgp.group_fusion_plating_cgp_designated_official', + ] + for xmlid in expected_old: + old_grp = self.env.ref(xmlid, raise_if_not_found=False) + if not old_grp: + continue # Module not installed + self.assertIn(old_grp, owner.trans_implied_ids, + f'Owner must transitively imply {xmlid} for backward-compat')