def test_should_process_object_fields(self): object_source_negative = b"""<?xml version="1.0" encoding="UTF-8"?> <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata"> <customSettingsType>Hierarchy</customSettingsType> <description>Description</description> <label>Test</label> </CustomObject>""" object_source_positive = b"""<?xml version="1.0" encoding="UTF-8"?> <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata"> <description>Description</description> <label>Test</label> </CustomObject>""" task = create_task(GenerateDataDictionary, {}) assert task._should_process_object_fields( "test__Obj__c", metadata_tree.fromstring(object_source_positive)) assert task._should_process_object_fields("test__Obj__c", None) assert task._should_process_object_fields( "Account", metadata_tree.fromstring(object_source_positive)) assert not task._should_process_object_fields( "test__Obj__e", metadata_tree.fromstring(object_source_positive)) assert not task._should_process_object_fields( "test__Obj__c", metadata_tree.fromstring(object_source_negative))
def test_process_sfdx_release(self, fromstring): task = create_task( GenerateDataDictionary, { "object_path": "object.csv", "field_path": "fields.csv", "release_prefix": "rel/", }, ) zip_file = Mock() zip_file.read.return_value = "<test></test>" zip_file.namelist.return_value = [ "force-app/main/default/objects/Child__c.object-meta.xml", "force-app/main/default/objects/Child__c/fields/Lookup__c.field-meta.xml", "force-app/main/default/objects/Parent__c.object-meta.xml", ".gitignore", "test__c.object-meta.xml", ] task._process_object_element = Mock() task._process_field_element = Mock() task._process_sfdx_release(zip_file, LooseVersion("1.1")) zip_file.read.assert_has_calls( [ call("force-app/main/default/objects/Child__c.object-meta.xml"), call( "force-app/main/default/objects/Child__c/fields/Lookup__c.field-meta.xml" ), call("force-app/main/default/objects/Parent__c.object-meta.xml"), ] ) task._process_object_element.assert_has_calls( [ call( "Child__c", metadata_tree.fromstring("<test></test>"), LooseVersion("1.1"), ), call( "Parent__c", metadata_tree.fromstring("<test></test>"), LooseVersion("1.1"), ), ] ) task._process_field_element.assert_has_calls( [ call( "Child__c", metadata_tree.fromstring("<test></test>"), LooseVersion("1.1"), ) ] )
def test_repr(self): Data = fromstring(standard_xml) for foo in Data.foo: assert repr(foo) for bar in Data.bar: assert repr(bar) Data = fromstring(f"<Data xmlns='{METADATA_NAMESPACE}'></Data>") assert repr(Data) == "<Data></Data> element"
def _process_sfdx_release(self, zip_file, version): """Process an SFDX ZIP file for objects and fields""" for f in zip_file.namelist(): path = PurePosixPath(f) if f.startswith("force-app/main/default/objects"): if path.suffixes == [".object-meta", ".xml"]: sobject_name = path.name[:-len(".object-meta.xml")] if sobject_name.count("__") == 1: sobject_name = f"{version.package.namespace}{sobject_name}" element = metadata_tree.fromstring(zip_file.read(f)) if self._should_process_object(version.package.namespace, sobject_name, element): self._process_object_element(sobject_name, element, version) else: # If this is an object type from which we shouldn't process any fields, # track it in omit_sobjects so we can drop any fields later if we don't have # the right information at time of processing. # Note that the owning object may be in a dependency package, so we won't find it below. if not self._should_process_object_fields( sobject_name, element): self.omit_sobjects.add(sobject_name) elif path.suffixes == [".field-meta", ".xml"]: # To get the sObject name, we need to remove the `/fields/SomeField.field-meta.xml` # and take the last path component # Find the sObject metadata file sobject_name = f"{path.parent.parent.stem}" sobject_file = str(path.parent.parent / f"{sobject_name}.object-meta.xml") if sobject_name.count("__") == 1: sobject_name = f"{version.package.namespace}{sobject_name}" # If the object-meta file is locatable, load it so we can check # if this is a Custom Setting. if sobject_file in zip_file.namelist(): object_entity = metadata_tree.fromstring( zip_file.read(sobject_file)) else: object_entity = None if self._should_process_object_fields( sobject_name, object_entity): self._process_field_element( sobject_name, metadata_tree.fromstring(zip_file.read(f)), version, )
def test_transform_entity__child_not_found(self): api_name = "Supercalifragilisticexpialidocious__c" entity = "CustomObject" tag = "customObjectAttribute" value = "newAttributeValue" ORIGINAL_XML = f"""<?xml version="1.0" encoding="UTF-8"?> <{entity} xmlns="http://soap.sforce.com/2006/04/metadata"> <name>{api_name}</name> <anotherTag>value</anotherTag> </{entity}> """ EXPECTED_XML = f"""<?xml version="1.0" encoding="UTF-8"?> <{entity} xmlns="http://soap.sforce.com/2006/04/metadata"> <name>{api_name}</name> <anotherTag>value</anotherTag> <{tag}>{value}</{tag}> </{entity}> """ metadata = fromstring(ORIGINAL_XML.encode("utf-8")) expected_metadata = fromstring(EXPECTED_XML.encode("utf-8")) task = create_task( UpdateMetadataFirstChildTextTask, { "managed": False, "namespace_inject": None, "metadata_type": entity, "tag": tag, "value": value, }, ) task.logger = mock.Mock() assert tag == task.options.get("tag") assert value == task.options.get("value") actual = task._transform_entity(metadata, api_name) assert metadata == actual assert actual.tostring() == expected_metadata.tostring() task.logger.info.assert_has_calls([ mock.call(f'Updating {entity} "{api_name}":'), mock.call(f' {tag} as "{value}"'), ])
def test_process_field_element__updated(self): xml_source = """<?xml version="1.0" encoding="UTF-8"?> <CustomField xmlns="http://soap.sforce.com/2006/04/metadata"> <fullName>Account__c</fullName> <inlineHelpText>{}</inlineHelpText> <label>Account</label> <type>Lookup</type> <referenceTo>Account</referenceTo> </CustomField> """ task = create_task( GenerateDataDictionary, { "object_path": "object.csv", "field_path": "fields.csv", "release_prefix": "rel/", }, ) task._init_schema() task._process_field_element( "Test__c", metadata_tree.fromstring( xml_source.format("Initial").encode("utf-8")), "1.1", ) assert task.schema["Test__c"]["fields"]["Account__c"] == { "version": LooseVersion("1.1"), "help_text": "Initial", "description": "", "label": "Account", "type": "Lookup", "valid_values": "->Account", } task._process_field_element( "Test__c", metadata_tree.fromstring(xml_source.format("New").encode("utf-8")), "1.2", ) assert task.schema["Test__c"]["fields"]["Account__c"] == { "version": LooseVersion("1.1"), "help_text": "New", "description": "", "label": "Account", "type": "Lookup", "valid_values": "->Account", }
def test_add_before__missing(self): task = create_task( AddPicklistEntries, { "api_version": "47.0", "picklists": ["MyObject.Time_Zone__c", "MyObject2.Type__c"], "entries": [ {"fullName": "Test", "add_before": "Not-there"}, {"fullName": "Foo", "label": "Bar", "default": True}, ], "record_types": ["Default_RT"], }, ) tree = metadata_tree.fromstring(OBJECT_XML) result = task._transform_entity(tree, "MyObject") vsd = result.find("fields", fullName="Time_Zone__c").valueSet.valueSetDefinition values = vsd.value test_elem = next(v for v in values if v.fullName.text == "Test") assert vsd._element.index(test_elem._element) == 3 # The `sorted` element is 0 test_elem = next(v for v in values if v.fullName.text == "Foo") assert vsd._element.index(test_elem._element) == 4 rt_picklist = result.find("recordTypes", fullName="Default_RT").find( "picklistValues", picklist="Time_Zone__c" ) rt_values = rt_picklist.values test_elem = next(v for v in rt_values if v.fullName.text == "Test") assert ( rt_picklist._element.index(test_elem._element) == 3 ) # The `picklist` element is 0 test_elem = next(v for v in rt_values if v.fullName.text == "Foo") assert rt_picklist._element.index(test_elem._element) == 4
def test_adds_record_type_entries__multiple(self): task = create_task( AddPicklistEntries, { "api_version": "47.0", "picklists": ["MyObject.Time_Zone__c", "MyObject2.Type__c"], "entries": [ {"fullName": "Test"}, {"fullName": "Foo", "label": "Bar", "default": True}, ], "record_types": ["Default_RT", "Second_RT"], }, ) # Validate that the entries are added to the Record Type tree = metadata_tree.fromstring(OBJECT_XML) result = task._transform_entity(tree, "MyObject2") for rt_name in ["Default_RT", "Second_RT"]: # Make sure we added the picklist values values = ( result.find("recordTypes", fullName=rt_name) .find("picklistValues", picklist="Type__c") .values ) assert "Test" in (v.fullName.text for v in values) assert "Foo" in (v.fullName.text for v in values)
def test_process_field_element__valid_values_global_value_set(self): xml_source = """<?xml version="1.0" encoding="UTF-8"?> <CustomField xmlns="http://soap.sforce.com/2006/04/metadata"> <fullName>Type__c</fullName> <label>Type</label> <type>Picklist</type> <valueSet> <valueSetName>Test Value Set</valueSetName> </valueSet> </CustomField> """ task = create_task(GenerateDataDictionary, {}) task._init_schema() p = Package(None, "Test", "test__", "rel/") v = PackageVersion(p, StrictVersion("1.1")) task._process_field_element( "test__Test__c", metadata_tree.fromstring(xml_source.encode("utf-8")), v) assert task.fields["test__Test__c.test__Type__c"] == [ FieldDetail( v, "test__Test__c", "test__Type__c", "Type", "Picklist", "", "", "Global Value Set Test Value Set", ) ]
def test_process_field_element__master_detail(self): xml_source = """<?xml version="1.0" encoding="UTF-8"?> <CustomField xmlns="http://soap.sforce.com/2006/04/metadata"> <fullName>Lookup__c</fullName> <label>Test</label> <type>MasterDetail</type> <referenceTo>Test__c</referenceTo> </CustomField> """ task = create_task(GenerateDataDictionary, {}) p = Package(None, "Test", "test__", "rel/") v = PackageVersion(p, StrictVersion("1.1")) task._init_schema() task._process_field_element( "test__Test__c", metadata_tree.fromstring(xml_source.encode("utf-8")), v) assert "test__Test__c.test__Lookup__c" in task.fields assert task.fields["test__Test__c.test__Lookup__c"] == [ FieldDetail( v, "test__Test__c", "test__Lookup__c", "Test", "Master-Detail Relationship to test__Test__c", "", "", "", ) ]
def test_process_field_element__new(self): xml_source = """<?xml version="1.0" encoding="UTF-8"?> <CustomField xmlns="http://soap.sforce.com/2006/04/metadata"> <fullName>Account__c</fullName> <label>Account</label> <type>Lookup</type> </CustomField> """ task = create_task( GenerateDataDictionary, { "object_path": "object.csv", "field_path": "fields.csv", "release_prefix": "rel/", }, ) task._init_schema() task._process_field_element( "Test__c", metadata_tree.fromstring(xml_source.encode("utf-8")), "1.1" ) assert "Test__c" in task.schema assert "Account__c" in task.schema["Test__c"]["fields"] assert task.schema["Test__c"]["fields"]["Account__c"] == { "version": LooseVersion("1.1"), "help_text": "", "label": "Account", "type": "Lookup", "picklist_values": "", }
def test_adds_correct_number_of_values(self): task = create_task( AddValueSetEntries, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "entries": [ { "fullName": "Test", "label": "Label" }, { "fullName": "Test_2", "label": "Label 2" }, { "fullName": "Other", "label": "Duplicate" }, ], }, ) mdtree = metadata_tree.fromstring(VALUESET_XML) xml_tree = mdtree._element assert len(xml_tree.findall(f".//{MD}standardValue")) == 2 task._transform_entity(mdtree, "ValueSet") assert len(xml_tree.findall(f".//{MD}standardValue")) == 4
def test_raises_exception_missing_values(self): task = create_task( AddValueSetEntries, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "entries": [{ "fullName": "Value" }], }, ) tree = metadata_tree.fromstring(VALUESET_XML) with pytest.raises(TaskOptionsError): task._transform_entity(tree, "ValueSet") task = create_task( AddValueSetEntries, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "entries": [{ "label": "Value" }], }, ) with pytest.raises(TaskOptionsError): task._transform_entity(tree, "ValueSet")
def test_sets_helptext_for_standard_field_fields(self): task = create_task( SetFieldHelpText, { "api_version": "47.0", "overwrite": True, "fields": [ { "api_name": "MyObject.Foo", "help_text": "foo" }, { "api_name": "MyObject.Bar", "help_text": "bar" }, ], }, ) tree = metadata_tree.fromstring(STANDARD_OBJECT_XML) result = task._transform_entity(tree, "MyObject") test_elem = result.find("fields", fullName="Foo") assert test_elem is not None assert test_elem.inlineHelpText.text == "foo" test_elem = result.find("fields", fullName="Bar") assert test_elem is not None assert test_elem.inlineHelpText.text == "bar"
def test_upserts_existing_class_permission(self): task = create_task( AddPermissionSetPermissions, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "class_accesses": [{"apexClass": "ApexController", "enabled": True}], }, ) tree = metadata_tree.fromstring(PERMSET_XML) element = tree._element assert ( len( element.findall(f".//{MD}classAccesses[{MD}apexClass='ApexController']") ) == 1 ) task._transform_entity(tree, "PermSet")._element classAccesses = element.findall( f".//{MD}classAccesses[{MD}apexClass='ApexController']" ) assert len(classAccesses) == 1 enabled = classAccesses[0].findall(f".//{MD}enabled") assert len(enabled) == 1 assert enabled[0].text == "true"
def test_process_field_element__valid_values_global_value_set(self): xml_source = """<?xml version="1.0" encoding="UTF-8"?> <CustomField xmlns="http://soap.sforce.com/2006/04/metadata"> <fullName>Type__c</fullName> <label>Type</label> <type>Picklist</type> <valueSet> <valueSetName>Test Value Set</valueSetName> </valueSet> </CustomField> """ task = create_task( GenerateDataDictionary, { "object_path": "object.csv", "field_path": "fields.csv", "release_prefix": "rel/", }, ) task._init_schema() task._process_field_element( "Test__c", metadata_tree.fromstring(xml_source.encode("utf-8")), "1.1") assert task.schema["Test__c"]["fields"]["Type__c"] == { "version": LooseVersion("1.1"), "help_text": "", "label": "Type", "description": "", "type": "Picklist", "valid_values": "Global Value Set Test Value Set", }
def test_add_single_object_multi_field_no_help_text(self): task = create_task( SetFieldHelpText, { "api_version": "47.0", "overwrite": True, "fields": [ { "api_name": "MyObject4.Foo__c", "help_text": "foo" }, { "api_name": "MyObject4.Bar__c", "help_text": "bar" }, ], }, ) tree = metadata_tree.fromstring(OBJECT_XML_4) result = task._transform_entity(tree, "MyObject4") # Validate that the first sObject has one picklist changed test_elem = result.find("fields", fullName="Foo__c") assert test_elem is not None assert test_elem.inlineHelpText.text == "foo" # Validate that the first sObject alters only the custom field listed test_elem = result.find("fields", fullName="Bar__c") assert test_elem is not None assert test_elem.inlineHelpText.text == "bar"
def test_namespaced_to_string__output_namespaces(self): CustomMetadata = fromstring(standard_xml) xml_out = (CustomMetadata.find("bar").find("name").tostring( xml_declaration=True, include_parent_namespaces=True)) expected_out = """<?xml version="1.0" encoding="UTF-8"?> <name xmlns="http://soap.sforce.com/2006/04/metadata">Bar1</name>""" assert (" ".join(xml_out.split()).strip() == " ".join( expected_out.split()).strip()), xml_out.strip()
def _generate_package_xml(self, operation): if operation is MetadataOperation.RETRIEVE: with open(self.package_xml_path, "r") as f: package_xml_content = f.read() package_xml_content = package_xml_content.format( **self.namespace_prefixes) if (self.options["include_packaged_objects"] or "package_xml" not in self.options): # We need to rewrite the package.xml for one or two reasons. # Either we are using packaged-object expansion, or we're using # the built-in admin_profile.xml and need to substitute in # profile API names. # Convert to bytes because stored `package.xml`s typically have an encoding declaration, # which `fromstring()` doesn't like. package_xml = metadata_tree.fromstring( package_xml_content.encode("utf-8")) if self.options["include_packaged_objects"]: self._expand_package_xml(package_xml) if "package_xml" not in self.options: self._expand_profile_members(package_xml) package_xml_content = package_xml.tostring( xml_declaration=True) return package_xml_content else: return super()._generate_package_xml(operation)
def test_error_handling(self): Data = fromstring( f"<Data xmlns='{METADATA_NAMESPACE}'><Foo/></Data>\n") Data.Foo with pytest.raises(AttributeError) as e: assert Data.Bar assert "not found in" in str(e.value)
def test_includes_buttons(self): task = create_task( AddRelatedLists, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "related_list": "TEST", "fields": "foo__c,bar__c", "custom_buttons": "MyCustomNewAction,MyCustomEditAction", }, ) tree = metadata_tree.fromstring( LAYOUT_XML.format(relatedLists=RELATED_LIST).encode("utf-8")) assert (len( tree._element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']")) == 0) result = task._transform_entity(tree, "Layout") element = result._element assert len( element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']")) == 1 button_elements = element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']/{MD}customButtons") custom_buttons = {elem.text for elem in button_elements} assert custom_buttons == set( ["MyCustomNewAction", "MyCustomEditAction"])
def test_excludes_buttons(self): task = create_task( AddRelatedLists, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "related_list": "TEST", "fields": "foo__c,bar__c", "exclude_buttons": "New,Edit", }, ) tree = metadata_tree.fromstring( LAYOUT_XML.format(relatedLists=RELATED_LIST).encode("utf-8")) assert (len( tree._element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']")) == 0) result = task._transform_entity(tree, "Layout") assert (len( result._element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']")) == 1) button_elements = result._element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']/{MD}excludeButtons") excluded_buttons = {elem.text for elem in button_elements} assert excluded_buttons == set(["New", "Edit"])
def test_adds_related_list(self): task = create_task( AddRelatedLists, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "related_list": "TEST", "fields": "foo__c,bar__c", }, ) tree = metadata_tree.fromstring( LAYOUT_XML.format(relatedLists=RELATED_LIST).encode("utf-8")) element = tree._element assert len( element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']")) == 0 task._transform_entity(tree, "Layout") assert len( element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']")) == 1 field_elements = element.findall( f".//{MD}relatedLists[{MD}relatedList='TEST']/{MD}fields") field_names = {elem.text for elem in field_elements} assert field_names == set(["foo__c", "bar__c"])
def test_raises_for_empty_fields(self): with pytest.raises(TaskOptionsError): task = create_task(SetFieldHelpText, { "api_version": "47.0", "fields": [] }) tree = metadata_tree.fromstring(OBJECT_XML) task._transform_entity(tree, "MyObject")
def test_sets_status(self): task = create_task(SetDuplicateRuleStatus, { "api_version": "47.0", "active": False }) tree = metadata_tree.fromstring(DUPERULE_XML) assert tree.find("isActive").text == "true" result = task._transform_entity(tree, "DupeRule") assert result.find("isActive").text == "false"
def _process_mdapi_release(self, zip_file, version): """Process an MDAPI ZIP file for objects and fields""" for f in zip_file.namelist(): path = PurePosixPath(f) if path.parent == PurePosixPath("src/objects") and path.suffix == ".object": sobject_name = path.stem self._process_object_element( sobject_name, metadata_tree.fromstring(zip_file.read(f)), version )
def test_append_goes_in_the_middle(self): Data = fromstring(f"""<Data xmlns='{METADATA_NAMESPACE}'> <foo>Foo</foo> <bar>Bar</bar> </Data>""") Data.append(tag="foo", text="Foo2") assert Data.foo[1].text == "Foo2" Data.append(tag="foo", text="Foo3") assert Data.foo[2].text == "Foo3" Data.append(tag="bar", text="Bar2") assert Data.bar[1].text == "Bar2"
def test_raises_for_missing_picklist(self): task = create_task( AddPicklistEntries, { "api_version": "47.0", "picklists": ["MyObject.Type2__c"], "entries": [{"fullName": "Test", "default": True}], }, ) tree = metadata_tree.fromstring(OBJECT_XML) with pytest.raises(TaskOptionsError): task._transform_entity(tree, "MyObject")
def test_multiple_namespaces(self): xml = """ <CustomMetadata xmlns="http://soap.sforce.com/2006/04/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <label>Account MD Isolation Rollup</label> <protected>false</protected> <values> <field>dlrs__Active__c</field> <value xsi:type="xsd:boolean">true</value> </values> </CustomMetadata>""".strip() CustomMetadata = fromstring(xml) assert xml.strip() == CustomMetadata.tostring().strip()
def test_adds_entry(self): task = create_task( AddValueSetEntries, { "managed": True, "api_version": "47.0", "api_names": "bar,foo", "entries": [ { "fullName": "Test", "label": "Label" }, { "fullName": "Test_2", "label": "Label 2" }, ], }, ) tree = etree.fromstring(VALUESET_XML).getroottree() assert len( tree.findall(f".//{MD}standardValue[{MD}fullName='Test']")) == 0 assert len( tree.findall(f".//{MD}standardValue[{MD}fullName='Test_2']")) == 0 result = task._transform_entity(metadata_tree.fromstring(VALUESET_XML), "ValueSet") entry = result._element.findall( f".//{MD}standardValue[{MD}fullName='Test']") assert len(entry) == 1 label = entry[0].findall(f".//{MD}label") assert len(label) == 1 assert label[0].text == "Label" default = entry[0].findall(f".//{MD}default") assert len(default) == 1 assert default[0].text == "false" entry = result._element.findall( f".//{MD}standardValue[{MD}fullName='Test_2']") assert len(entry) == 1 label = entry[0].findall(f".//{MD}label") assert len(label) == 1 assert label[0].text == "Label 2" default = entry[0].findall(f".//{MD}default") assert len(default) == 1 assert default[0].text == "false"