import json import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] CRM_PACK = REPO_ROOT / "salesforce-crm-standard" EXPECTED_LIFECYCLE_OBJECTS = { "Contact", "Lead", "ProcessDefinition", } EXPECTED_PROCESS_DEFINITION_HOOKS = { ("after_create_in_tx", "crm.approval-process-child-sync"), ("after_update_in_tx", "crm.approval-process-child-sync"), } EXPECTED_PERSON_OWNER_LIFECYCLE_HOOKS = { ("after_delete_in_tx", "connector.remote-identity.owner-delete"), ("after_update_in_tx", "connector.remote-identity.owner-delete"), } def load_json(path: Path): with path.open(encoding="utf-8") as handle: return json.load(handle) class RecordLifecyclePolicyContractsTest(unittest.TestCase): def test_record_lifecycle_policies_are_locked_pack_components(self): manifest = load_json(CRM_PACK / "pack.json") components = [ component for component in manifest.get("components", []) if component.get("kind") == "object_lifecycle_policy" ] by_key = {component["businessKey"]: component for component in components} self.assertEqual(set(by_key), EXPECTED_LIFECYCLE_OBJECTS) for object_api_name, component in by_key.items(): with self.subTest(object=object_api_name): self.assertEqual(component["manageability"], "locked") self.assertNotIn("payload", component) self.assertIn( {"kind": "object_spec", "businessKey": "standard_objects_spec:crm"}, component.get("dependsOn", []), ) path = CRM_PACK / component["path"] self.assertTrue(path.exists(), path) payload = load_json(path) self.assertEqual(payload["objectApiName"], object_api_name) self.assertIn("lifecycle", payload) self.assertGreater(len(payload["lifecycle"].get("hooks", [])), 0) def test_process_definition_lifecycle_policy_declares_approval_sync_hooks(self): payload = load_json( CRM_PACK / "policies" / "process_definition_lifecycle.json" ) hooks = { (hook.get("phase"), hook.get("capability")) for hook in payload["lifecycle"].get("hooks", []) } self.assertEqual(hooks, EXPECTED_PROCESS_DEFINITION_HOOKS) def test_person_lifecycle_policies_handle_deleted_owner_and_restore(self): for object_api_name, filename in { "Lead": "lead_remote_identity_owner_delete_lifecycle.json", "Contact": "contact_remote_identity_owner_delete_lifecycle.json", }.items(): with self.subTest(object=object_api_name): payload = load_json(CRM_PACK / "policies" / filename) self.assertEqual(payload["objectApiName"], object_api_name) hooks = { (hook.get("phase"), hook.get("capability")) for hook in payload["lifecycle"].get("hooks", []) } self.assertEqual(hooks, EXPECTED_PERSON_OWNER_LIFECYCLE_HOOKS) def test_record_lifecycle_policies_do_not_live_in_object_specs(self): specs = load_json(CRM_PACK / "objects" / "standard_objects_spec.json") offenders = [ spec["apiName"] for spec in specs if spec.get("accessPolicy", {}).get("lifecycle") ] self.assertEqual(offenders, []) if __name__ == "__main__": unittest.main()