import json import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] CRM_PACK = REPO_ROOT / "salesforce-crm-standard" CRM_V66_PACK = REPO_ROOT / "salesforce-crm-standard-v66" EXPECTED_LAYOUTS = { "Pricebook2": { "path": "layouts/pricebook2_page_layout.json", "compact": ["Name", "IsActive", "IsStandard"], }, "Product2": { "path": "layouts/product2_page_layout.json", "compact": ["Name", "ProductCode", "IsActive", "Family"], }, "Task": { "path": "layouts/task_page_layout.json", "compact": ["Subject", "Status", "Priority", "ActivityDate"], }, } KNOWN_V66_LAYOUT_DRIFT = { ("Account", "RecordTypeId", "absent-v66"), ("Campaign", "ActualCost", "createable"), ("Campaign", "ActualCost", "updateable"), ("Campaign", "BudgetedCost", "createable"), ("Campaign", "BudgetedCost", "updateable"), ("Campaign", "Description", "createable"), ("Campaign", "Description", "updateable"), ("Campaign", "EndDate", "createable"), ("Campaign", "EndDate", "updateable"), ("Campaign", "ExpectedRevenue", "createable"), ("Campaign", "ExpectedRevenue", "updateable"), ("Campaign", "IsActive", "createable"), ("Campaign", "IsActive", "updateable"), ("Campaign", "Name", "createable"), ("Campaign", "Name", "updateable"), ("Campaign", "NumberSent", "createable"), ("Campaign", "NumberSent", "updateable"), ("Campaign", "StartDate", "createable"), ("Campaign", "StartDate", "updateable"), ("Campaign", "Status", "createable"), ("Campaign", "Status", "updateable"), ("Campaign", "Type", "createable"), ("Campaign", "Type", "updateable"), ("Case", "CaseNumber", "createable"), ("Case", "CaseNumber", "updateable"), ("Contract", "ContractNumber", "createable"), ("Contract", "ContractNumber", "updateable"), ("Contract", "EndDate", "createable"), ("Contract", "EndDate", "updateable"), ("Contract", "Name", "absent-v66"), ("Contract", "RecordTypeId", "absent-v66"), ("Contract", "ShippingCity", "absent-v66"), ("Contract", "ShippingCountry", "absent-v66"), ("Contract", "ShippingPostalCode", "absent-v66"), ("Contract", "ShippingState", "absent-v66"), ("Contract", "ShippingStreet", "absent-v66"), ("Opportunity", "RecordTypeId", "absent-v66"), ("Order", "CurrencyIsoCode", "absent-v66"), ("Order", "OpportunityId", "absent-v66"), ("Order", "OrderNumber", "createable"), ("Order", "OrderNumber", "updateable"), ("Order", "QuoteId", "absent-v66"), ("Order", "TotalAmount", "createable"), ("Order", "TotalAmount", "updateable"), } def load_json(path: Path): with path.open(encoding="utf-8") as handle: return json.load(handle) def object_spec(api_name: str): for spec in load_json(CRM_PACK / "objects" / "standard_objects_spec.json"): if spec.get("apiName") == api_name: return spec raise AssertionError(f"{api_name} object spec missing") def v66_object_spec(api_name: str): payload = load_json(CRM_V66_PACK / "objects" / api_name / "spec.json") for spec in payload: if spec.get("apiName") == api_name: return spec raise AssertionError(f"{api_name} v66 object spec missing") def field_map(spec): return {field["apiName"]: field for field in spec.get("fields", [])} def layout_fields(layout): fields = set(layout.get("compactLayout", [])) for section in layout.get("sections", []): fields.update(section.get("fields", [])) return fields def page_layout_components(): return [ entry for entry in load_json(CRM_PACK / "pack.json").get("components", []) if entry.get("kind") == "page_layout" ] def component(kind: str, business_key: str): for entry in load_json(CRM_PACK / "pack.json").get("components", []): if entry.get("kind") == kind and entry.get("businessKey") == business_key: return entry raise AssertionError(f"{kind}/{business_key} component missing") class CRMDefaultLayoutBacklogContractsTest(unittest.TestCase): def test_backlog_objects_have_l1_authored_default_layouts(self): for object_api_name, expected in EXPECTED_LAYOUTS.items(): with self.subTest(object=object_api_name): layout = load_json(CRM_PACK / expected["path"]) self.assertEqual(layout["objectApiName"], object_api_name) self.assertTrue(layout["isDefault"]) self.assertEqual(layout["type"], "Detail") self.assertEqual(layout["compactLayout"], expected["compact"]) self.assertGreater(len(layout["sections"]), 0) def test_backlog_layouts_are_registered_as_defaults_only_components(self): for object_api_name, expected in EXPECTED_LAYOUTS.items(): with self.subTest(object=object_api_name): entry = component("page_layout", f"{object_api_name}/default") self.assertEqual(entry["path"], expected["path"]) self.assertEqual(entry["manageability"], "defaults_only") self.assertIn( { "kind": "related_list_rule", "businessKey": "standard_related_list_rules", }, entry.get("dependsOn", []), ) def test_declared_layout_fields_exist_on_object_specs(self): for object_api_name, expected in EXPECTED_LAYOUTS.items(): with self.subTest(object=object_api_name): spec = object_spec(object_api_name) fields = {field["apiName"] for field in spec.get("fields", [])} layout = load_json(CRM_PACK / expected["path"]) visible_fields = layout_fields(layout) self.assertTrue( visible_fields.issubset(fields), f"{object_api_name} layout fields missing from spec: {sorted(visible_fields - fields)}", ) def test_layout_visible_v66_read_only_fields_stay_non_writable(self): for object_api_name, expected in EXPECTED_LAYOUTS.items(): with self.subTest(object=object_api_name): spec_fields = field_map(object_spec(object_api_name)) v66_fields = field_map(v66_object_spec(object_api_name)) layout = load_json(CRM_PACK / expected["path"]) for field_name in sorted(layout_fields(layout)): reference = v66_fields.get(field_name) self.assertIsNotNone( reference, f"{object_api_name}.{field_name} is on the default layout but is absent from the v66 reference", ) actual = spec_fields[field_name] for attr in ("createable", "updateable"): if reference.get(attr) is False: self.assertIs( actual.get(attr), False, f"{object_api_name}.{field_name} must keep {attr}=false from the v66 reference", ) def test_registered_layout_fields_exist_on_object_specs(self): for entry in page_layout_components(): with self.subTest(layout=entry["path"]): layout = load_json(CRM_PACK / entry["path"]) object_api_name = layout["objectApiName"] spec_fields = field_map(object_spec(object_api_name)) for field_name in sorted(layout_fields(layout)): self.assertIn( field_name, spec_fields, f"{object_api_name}.{field_name} is on {entry['path']} but missing from the object spec", ) def test_registered_v66_layout_drift_is_classified(self): observed = set() for entry in page_layout_components(): layout = load_json(CRM_PACK / entry["path"]) object_api_name = layout["objectApiName"] v66_path = CRM_V66_PACK / "objects" / object_api_name / "spec.json" if not v66_path.exists(): continue spec_fields = field_map(object_spec(object_api_name)) v66_fields = field_map(v66_object_spec(object_api_name)) for field_name in sorted(layout_fields(layout)): reference = v66_fields.get(field_name) if reference is None: observed.add((object_api_name, field_name, "absent-v66")) continue actual = spec_fields[field_name] for attr in ("createable", "updateable"): if reference.get(attr) is False and actual.get(attr) is not False: observed.add((object_api_name, field_name, attr)) self.assertEqual( sorted(KNOWN_V66_LAYOUT_DRIFT), sorted(observed), "registered v66-backed layout/schema drift must stay explicit", ) if __name__ == "__main__": unittest.main()