import json import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PACK_ROOTS = ( ROOT / "packs" / "Salesforce-PingCAP-L2", ROOT / "packs" / "NOT-EDIT-final-merged-packs", ) PATCH_ROOT = ( ROOT / "docs" / "migration" / "patch-bundles" / "20260819_task_status_open_and_lead_relate_flow" ) DRAFT_FLOWS = ( "Account_Related_Leads_Based_on_Website_Domain_Create", "Account_Related_Leads_Based_on_Website_Domain_Update", "BN_Account_Lead_Relate_Leads_to_Accounts", "BN_Lead_Relate_to_Account_by_Email_Domain_Flow", "Opportunity_Auto_To_Launch_Stage", "Opportunity_Launch_Stage_Validation", "Technical_Validation_Is_Request_Products", ) HANDOFF_FLOW_IDS = { "BN_Account_Lead_Relate_Leads_to_Accounts": ( "flowdef_std_bn_account_lead_relate_leads_to_accounts" ), "BN_Lead_Relate_to_Account_by_Email_Domain_Flow": ( "flowdef_std_bn_lead_relate_to_account_by_email_domain_flow" ), "Lead_Assignment_Rule_modification_for_NA_EMEA": ( "flowdef_std_lead_assignment_rule_modification_for_na_emea" ), "Lead_SetCountryCodeFromCountry": "flowdef_std_lead_setcountrycodefromcountry", "Opportunity_Auto_To_Launch_Stage": ( "flowdef_std_opportunity_auto_to_launch_stage" ), "Opportunity_Close_Date_Auto_Set": ( "flowdef_std_opportunity_close_date_auto_set" ), "Opportunity_Launch_Stage_Validation": ( "flowdef_std_opportunity_launch_stage_validation" ), "Opportunity_Stage_To_Lunched": "flowdef_std_opportunity_stage_to_lunched", "Technical_Validation_Is_Request_Products": ( "flowdef_std_technical_validation_is_request_products" ), } class LegacyFlowDraftPolicyTest(unittest.TestCase): def test_selected_flows_ship_as_draft_in_source_and_final_pack(self): for pack_root in PACK_ROOTS: for developer_name in DRAFT_FLOWS: with self.subTest(pack=pack_root.name, flow=developer_name): path = pack_root / "flows" / f"{developer_name}.flow.json" payload = json.loads(path.read_text(encoding="utf-8")) self.assertEqual(payload.get("status"), "Draft") def test_active_flows_do_not_reference_non_active_pack_children(self): for pack_root in PACK_ROOTS: flows = { path.name.removesuffix(".flow.json"): json.loads( path.read_text(encoding="utf-8") ) for path in (pack_root / "flows").glob("*.flow.json") } for caller_name, payload in flows.items(): if payload.get("status") != "Active": continue for subflow in payload.get("subflows", []): child_name = subflow.get("flowName") child = flows.get(child_name) if child is None: continue with self.subTest( pack=pack_root.name, caller=caller_name, child=child_name, ): self.assertEqual(child.get("status"), "Active") def test_recovery_sql_hands_ownership_to_pack_without_copying_pack_state(self): sql = ( PATCH_ROOT / "002_release_nine_flow_definitions_to_pack_control.sql" ).read_text(encoding="utf-8") for developer_name, definition_id in HANDOFF_FLOW_IDS.items(): with self.subTest(flow=developer_name): self.assertIn(developer_name, sql) self.assertIn(definition_id, sql) for forbidden in ( "OriginalActiveVersionId", "PreparedActiveVersionId", "OriginalLatestVersionId", "OriginalVersionNumber", "OriginalStatus", "OriginalSource", "OriginalMetadataSha256", "DesiredStatus", "DesiredMetadataSha256", ): with self.subTest(forbidden=forbidden): self.assertNotIn(forbidden, sql) self.assertIn("fd.`IsCustomized` = 0", sql) self.assertIn("active.`Source` = 'custom'", sql) self.assertIn("@issue_5139_unreviewed_custom_latest_before", sql) self.assertIn("COALESCE(SHA2(latest.`Metadata`, 256), '')", sql) self.assertIn( "ac794c05e35286852f5856fee94377b04547598d32202b87922ac0b80e71f11c", sql, ) self.assertIn( "bf7d0ab18d8210b346cb2a3337a25ddb87dfc504b52e2b3f2ba601f155323182", sql, ) self.assertNotIn("UPDATE `Flow`", sql) def test_pre_catalog_readback_reports_ownership_not_pack_lifecycle(self): sql = ( PATCH_ROOT / "099_verify_task_status_and_lead_relate_flow.sql" ).read_text(encoding="utf-8") self.assertIn("flow_pack_ownership_handoff", sql) self.assertIn("fd.`IsCustomized`", sql) self.assertIn("active.`Source`", sql) self.assertNotIn("bn_account_lead_relate_v1", sql) self.assertNotIn("v1_status", sql) if __name__ == "__main__": unittest.main()