diff --git a/addons/cetmix_tower_server/tests/test_jet_waypoint_access.py b/addons/cetmix_tower_server/tests/test_jet_waypoint_access.py new file mode 100644 index 0000000..6156870 --- /dev/null +++ b/addons/cetmix_tower_server/tests/test_jet_waypoint_access.py @@ -0,0 +1,970 @@ +# Copyright (C) 2025 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import AccessError + +from .common_jets import TestTowerJetsCommon + + +class TestTowerJetWaypointAccess(TestTowerJetsCommon): + """ + Test access rules for Jet Waypoint model + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Use existing users from common.py (cls.user, cls.manager, cls.root) + # Create additional manager for multi-manager tests + cls.manager2 = cls.Users.create( + { + "name": "Test Manager 2", + "login": "test_manager_2", + "email": "test_manager_2@example.com", + "groups_id": [(6, 0, [cls.group_manager.id])], + } + ) + + # ====================== + # Manager Read Access Tests + # ====================== + + def test_manager_read_access_jet_user_ids(self): + """Test Manager: Read when user is added in jet's user_ids""" + # Use existing jet and add manager to user_ids + self.jet_test.write({"user_ids": [(4, self.manager.id)]}) + jet = self.jet_test + + record = self.JetWaypoint.create( + { + "name": "Waypoint with User Access", + "reference": "waypoint_user_access", + "jet_id": jet.id, + "waypoint_template_id": self.waypoint_template.id, + } + ) + + records = self.JetWaypoint.with_user(self.manager).search( + [("id", "=", record.id)] + ) + self.assertEqual( + len(records), + 1, + "Manager should be able to read when added to jet's user_ids", + ) + + def test_manager_read_access_jet_manager_ids(self): + """Test Manager: Read when user is added in jet's manager_ids""" + # Use existing jet and add manager to manager_ids + self.jet_test.write({"manager_ids": [(4, self.manager.id)]}) + jet = self.jet_test + + record = self.JetWaypoint.create( + { + "name": "Waypoint with Manager Access", + "reference": "waypoint_manager_access", + "jet_id": jet.id, + "waypoint_template_id": self.waypoint_template.id, + } + ) + + records = self.JetWaypoint.with_user(self.manager).search( + [("id", "=", record.id)] + ) + self.assertEqual( + len(records), + 1, + "Manager should be able to read when added to jet's manager_ids", + ) + + def test_manager_read_no_access_root_level(self): + """Test Manager: No read access for Root level (3) even with jet access""" + # Use existing jet and add manager to manager_ids (has jet access) + self.jet_test.write({"manager_ids": [(4, self.manager.id)]}) + jet = self.jet_test + + # Create waypoint template with Root level + waypoint_template_root = self.JetWaypointTemplate.create( + { + "name": "Root Level Template", + "reference": "root_level_template", + "jet_template_id": self.jet_template_test.id, + "access_level": "3", # Root level + } + ) + + record = self.JetWaypoint.create( + { + "name": "Root Level Waypoint", + "reference": "root_level_waypoint", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template_root.id, + "access_level": "3", # Explicitly set Root level + } + ) + + records = self.JetWaypoint.with_user(self.manager).search( + [("id", "=", record.id)] + ) + self.assertEqual( + len(records), + 0, + "Manager should not read access_level='3' " + "even when in jet's manager_ids (Root level blocks access)", + ) + + def test_manager_read_no_access_not_in_jet(self): + """Test Manager: No read access when not in jet's Users or Managers""" + # Use existing jet (manager not in user_ids/manager_ids) + jet = self.jet_test + + record = self.JetWaypoint.create( + { + "name": "No Access Waypoint", + "reference": "no_access_waypoint", + "jet_id": jet.id, + "waypoint_template_id": self.waypoint_template.id, + } + ) + + records = self.JetWaypoint.with_user(self.manager).search( + [("id", "=", record.id)] + ) + self.assertEqual( + len(records), + 0, + "Manager should not read when not in jet's user_ids or manager_ids", + ) + + # ====================== + # Manager Write/Create Access Tests + # ====================== + + def test_manager_write_access_level_and_template_manager_ids(self): + """Test Manager: Write when access_level <= 2 AND in template's manager_ids""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.create( + { + "name": "Manager Can Write", + "reference": "manager_can_write", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + # Manager should be able to write + try: + record.with_user(self.manager).write({"name": "Updated Name"}) + record.invalidate_recordset() + self.assertEqual( + record.name, "Updated Name", "Manager should be able to update" + ) + except AccessError: + self.fail("Manager should be able to update when in template's manager_ids") + + def test_manager_write_forbidden_not_in_template_manager_ids(self): + """Test Manager: No write when not in template's manager_ids""" + # Create jet template without manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": False, + } + ) + + # Create jet with manager in manager_ids (for read access) + jet = self._create_jet( + name="No Write Jet", + reference="no_write_jet", + template=jet_template, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.create( + { + "name": "No Write Access", + "reference": "no_write_access", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + with self.assertRaises(AccessError): + record.with_user(self.manager).write({"name": "Should Fail"}) + + def test_manager_write_forbidden_root_level(self): + """Test Manager: No write when access_level is Root (3)""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template with Root level + waypoint_template_root = self.JetWaypointTemplate.create( + { + "name": "Root Level Template", + "reference": "root_level_template", + "jet_template_id": jet_template.id, + "access_level": "3", # Root level + } + ) + + record = self.JetWaypoint.create( + { + "name": "Root Level No Write", + "reference": "root_level_no_write", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template_root.id, + "access_level": "3", # Explicitly set Root level + } + ) + + with self.assertRaises(AccessError): + record.with_user(self.manager).write({"name": "Should Fail"}) + + def test_manager_create_access(self): + """Test Manager: Create when access_level <= 2 AND in template's manager_ids""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + # Try to create without being in template's manager_ids - should fail + jet_template_no_access = self.JetTemplate.create( + { + "name": "No Access Template", + "reference": "no_access_template", + "manager_ids": False, + } + ) + + jet_no_access = self._create_jet( + name="No Access Jet", + reference="no_access_jet", + template=jet_template_no_access, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], # Manager in jet but not template + ) + + waypoint_template_no_access = self.JetWaypointTemplate.create( + { + "name": "No Access Waypoint Template", + "reference": "no_access_waypoint_template", + "jet_template_id": jet_template_no_access.id, + } + ) + + with self.assertRaises(AccessError): + self.JetWaypoint.with_user(self.manager).create( + { + "name": "Create Fail", + "reference": "create_fail", + "jet_id": jet_no_access.id, + "waypoint_template_id": waypoint_template_no_access.id, + } + ) + + # Create with manager in template's manager_ids - should succeed + try: + record = self.JetWaypoint.with_user(self.manager).create( + { + "name": "Create Success", + "reference": "create_success", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + records = self.JetWaypoint.search([("id", "=", record.id)]) + self.assertEqual(len(records), 1, "Manager should be able to create") + except AccessError: + self.fail("Manager should be able to create when in template's manager_ids") + + # ====================== + # Manager Delete Access Tests + # ====================== + + def test_manager_delete_own_record(self): + """Test Manager: Delete own record when in template's manager_ids""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.with_user(self.manager).create( + { + "name": "My Record", + "reference": "my_record", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + try: + record.with_user(self.manager).unlink() + records = self.JetWaypoint.search([("id", "=", record.id)]) + self.assertEqual( + len(records), 0, "Manager should be able to delete own record" + ) + except AccessError: + self.fail("Manager should be able to delete own record") + + def test_manager_delete_not_creator(self): + """Test Manager: Cannot delete record created by another user""" + # Create jet template with both managers in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id), (4, self.manager2.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.with_user(self.manager2).create( + { + "name": "Other's Record", + "reference": "others_record", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + # Manager1 cannot delete Manager2's record + with self.assertRaises(AccessError): + record.with_user(self.manager).unlink() + + def test_manager_delete_not_in_template_manager_ids(self): + """Test Manager: Cannot delete when not in template's manager_ids""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Delete Not In Template Jet", + reference="delete_not_in_template_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.with_user(self.manager).create( + { + "name": "Removed Manager", + "reference": "removed_manager", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + # Remove from template's manager_ids + jet_template.write({"manager_ids": False}) + + # Cannot delete anymore + with self.assertRaises(AccessError): + record.with_user(self.manager).unlink() + + def test_manager_delete_root_level(self): + """Test Manager: Cannot delete Root level record""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template with Root level + waypoint_template_root = self.JetWaypointTemplate.create( + { + "name": "Root Level Template", + "reference": "root_level_template", + "jet_template_id": jet_template.id, + "access_level": "3", # Root level + } + ) + + # Create record with Root level as root (default user) + record = self.JetWaypoint.create( + { + "name": "Root Level Delete", + "reference": "root_level_delete", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template_root.id, + } + ) + + with self.assertRaises(AccessError): + record.with_user(self.manager).unlink() + + # ====================== + # Root Access Tests + # ====================== + + def test_root_full_access(self): + """ + Test Root: Full CRUD access regardless of access_level or creator. + + Root has unrestricted access to all records via security rule + [(1, '=', 1)], so we test: + - Create records with all access levels + - Read records with all access levels + - Write to records with all access levels + - Delete records regardless of creator + """ + # Create jet template for testing + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + } + ) + + # Create jet from this template with unique name + jet = self._create_jet( + name="Write Access Jet", + reference="write_access_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Test CRUD operations for all access levels (only Manager and Root exist) + for access_level in ["2", "3"]: + # Create waypoint template with specific access level + waypoint_template = self.JetWaypointTemplate.create( + { + "name": f"Template Level {access_level}", + "reference": f"template_level_{access_level}", + "jet_template_id": jet_template.id, + "access_level": access_level, + } + ) + + # Root can create any level + record = self.JetWaypoint.with_user(self.root).create( + { + "name": f"Root Level {access_level}", + "reference": f"root_level_{access_level}", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + # Root can read any level + records = self.JetWaypoint.with_user(self.root).search( + [("id", "=", record.id)] + ) + self.assertEqual( + len(records), + 1, + f"Root should be able to read access_level={access_level}", + ) + + # Root can write any level + record.with_user(self.root).write( + {"name": f"Root Updated Level {access_level}"} + ) + record.invalidate_recordset() + self.assertEqual( + record.name, + f"Root Updated Level {access_level}", + f"Root should be able to update access_level={access_level}", + ) + + # Test Root can delete records created by other users + # Add manager to template's manager_ids so they can create the record + jet_template.write({"manager_ids": [(4, self.manager.id)]}) + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Manager Template", + "reference": "manager_template", + "jet_template_id": jet_template.id, + } + ) + manager_record = self.JetWaypoint.with_user(self.manager).create( + { + "name": "Manager's Record", + "reference": "managers_record", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + manager_record.with_user(self.root).unlink() + records = self.JetWaypoint.with_user(self.root).search( + [("id", "=", manager_record.id)] + ) + self.assertEqual( + len(records), + 0, + "Root should be able to delete records from any creator", + ) + + # ====================== + # Edge Cases + # ====================== + + def test_access_level_changes_visibility(self): + """Test that changing access_level affects visibility""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet with manager in manager_ids with unique name + jet = self._create_jet( + name="Access Level Changes Jet", + reference="access_level_changes_jet", + template=jet_template, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], + ) + + # Create waypoint template with Manager level + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + "access_level": "2", + } + ) + + record = self.JetWaypoint.create( + { + "name": "Changing Level", + "reference": "changing_level", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + } + ) + + # Manager can read + records = self.JetWaypoint.with_user(self.manager).search( + [("id", "=", record.id)] + ) + self.assertEqual(len(records), 1, "Manager should read level 2") + + # Change template to Root level + waypoint_template.write({"access_level": "3"}) + # Update waypoint's access_level since it's stored and doesn't auto-update + record.write({"access_level": "3"}) + record.invalidate_recordset() + + # Manager cannot read anymore + records = self.JetWaypoint.with_user(self.manager).search( + [("id", "=", record.id)] + ) + self.assertEqual(len(records), 0, "Manager should not read level 3") + + def test_manager_prepare_forbidden_no_write_access(self): + """Test Manager: Cannot prepare waypoint without write access""" + # Create jet template without manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": False, + } + ) + + # Create jet with manager in manager_ids (for read access) + jet = self._create_jet( + name="Prepare Forbidden Jet", + reference="prepare_forbidden_jet", + template=jet_template, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.create( + { + "name": "Prepare Forbidden", + "reference": "prepare_forbidden", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + "state": "draft", + } + ) + + # Manager should not be able to prepare without write access + with self.assertRaises(AccessError): + record.with_user(self.manager).prepare() + + def test_manager_prepare_forbidden_root_level(self): + """Test Manager: Cannot prepare waypoint with Root level""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template + jet = self._create_jet( + name="Prepare Root Level Jet", + reference="prepare_root_level_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template with Root level + waypoint_template_root = self.JetWaypointTemplate.create( + { + "name": "Root Level Template", + "reference": "root_level_template", + "jet_template_id": jet_template.id, + "access_level": "3", # Root level + } + ) + + record = self.JetWaypoint.create( + { + "name": "Root Level Prepare", + "reference": "root_level_prepare", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template_root.id, + "access_level": "3", # Explicitly set Root level + "state": "draft", + } + ) + + # Manager should not be able to prepare Root level waypoint + with self.assertRaises(AccessError): + record.with_user(self.manager).prepare() + + def test_manager_fly_to_forbidden_no_write_access(self): + """Test Manager: Cannot fly_to waypoint without write access""" + # Create jet template without manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": False, + } + ) + + # Create jet with manager in manager_ids (for read access) + jet = self._create_jet( + name="Fly To Forbidden Jet", + reference="fly_to_forbidden_jet", + template=jet_template, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.create( + { + "name": "Fly To Forbidden", + "reference": "fly_to_forbidden", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + "state": "ready", + } + ) + + # Manager should not be able to fly_to without write access + with self.assertRaises(AccessError): + record.with_user(self.manager).fly_to() + + def test_manager_fly_to_forbidden_root_level(self): + """Test Manager: Cannot fly_to waypoint with Root level""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Create jet from this template + jet = self._create_jet( + name="Fly To Root Level Jet", + reference="fly_to_root_level_jet", + template=jet_template, + server=self.server_test_1, + ) + + # Create waypoint template with Root level + waypoint_template_root = self.JetWaypointTemplate.create( + { + "name": "Root Level Template", + "reference": "root_level_template", + "jet_template_id": jet_template.id, + "access_level": "3", # Root level + } + ) + + record = self.JetWaypoint.create( + { + "name": "Root Level Fly To", + "reference": "root_level_fly_to", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template_root.id, + "access_level": "3", # Explicitly set Root level + "state": "ready", + } + ) + + # Manager should not be able to fly_to Root level waypoint + with self.assertRaises(AccessError): + record.with_user(self.manager).fly_to() + + def test_manager_prepare_success_with_write_access(self): + """Test Manager: Can prepare waypoint with write access""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Ensure manager has server access + self.server_test_1.write({"user_ids": [(4, self.manager.id)]}) + + # Create jet from this template with manager in manager_ids + jet = self._create_jet( + name="Prepare Success Jet", + reference="prepare_success_jet", + template=jet_template, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.create( + { + "name": "Prepare Success", + "reference": "prepare_success", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + "state": "draft", + } + ) + + # Manager should be able to prepare with write access + try: + result = record.with_user(self.manager).prepare() + self.assertTrue(result, "Manager should be able to prepare") + record.invalidate_recordset() + # State should be ready (no plan_create_id) + self.assertEqual(record.state, "ready", "State should be ready") + except AccessError: + self.fail( + "Manager should be able to prepare when in template's manager_ids" + ) + + def test_manager_fly_to_success_with_write_access(self): + """Test Manager: Can fly_to waypoint with write access""" + # Create jet template with manager in manager_ids + jet_template = self.JetTemplate.create( + { + "name": "Test Template", + "reference": "test_template", + "manager_ids": [(4, self.manager.id)], + } + ) + + # Ensure manager has server access + self.server_test_1.write({"user_ids": [(4, self.manager.id)]}) + + # Create jet from this template with manager in manager_ids + jet = self._create_jet( + name="Fly To Success Jet", + reference="fly_to_success_jet", + template=jet_template, + server=self.server_test_1, + manager_ids=[(4, self.manager.id)], + ) + + # Create waypoint template + waypoint_template = self.JetWaypointTemplate.create( + { + "name": "Test Waypoint Template", + "reference": "test_waypoint_template", + "jet_template_id": jet_template.id, + } + ) + + record = self.JetWaypoint.create( + { + "name": "Fly To Success", + "reference": "fly_to_success", + "jet_id": jet.id, + "waypoint_template_id": waypoint_template.id, + "state": "ready", + } + ) + + # Manager should be able to fly_to with write access + try: + result = record.with_user(self.manager).fly_to() + self.assertTrue(result, "Manager should be able to fly_to") + record.invalidate_recordset() + # State should be current (no previous waypoint, no plan_arrive_id) + self.assertEqual(record.state, "current", "State should be current") + except AccessError: + self.fail("Manager should be able to fly_to when in template's manager_ids")