cumulusci-plus 5.0.21__py3-none-any.whl → 5.0.43__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cumulusci/__about__.py +1 -1
- cumulusci/cli/logger.py +2 -2
- cumulusci/cli/service.py +20 -0
- cumulusci/cli/task.py +19 -3
- cumulusci/cli/tests/test_error.py +3 -1
- cumulusci/cli/tests/test_flow.py +279 -2
- cumulusci/cli/tests/test_org.py +5 -0
- cumulusci/cli/tests/test_service.py +15 -12
- cumulusci/cli/tests/test_task.py +122 -2
- cumulusci/cli/tests/utils.py +1 -4
- cumulusci/core/config/__init__.py +1 -0
- cumulusci/core/config/base_task_flow_config.py +26 -1
- cumulusci/core/config/org_config.py +2 -1
- cumulusci/core/config/project_config.py +14 -20
- cumulusci/core/config/scratch_org_config.py +12 -0
- cumulusci/core/config/tests/test_config.py +1 -0
- cumulusci/core/config/tests/test_config_expensive.py +9 -3
- cumulusci/core/config/universal_config.py +3 -4
- cumulusci/core/dependencies/base.py +5 -1
- cumulusci/core/dependencies/dependencies.py +1 -1
- cumulusci/core/dependencies/github.py +1 -2
- cumulusci/core/dependencies/resolvers.py +1 -1
- cumulusci/core/dependencies/tests/test_dependencies.py +1 -1
- cumulusci/core/dependencies/tests/test_resolvers.py +1 -1
- cumulusci/core/flowrunner.py +90 -6
- cumulusci/core/github.py +1 -1
- cumulusci/core/sfdx.py +3 -1
- cumulusci/core/source_transforms/tests/test_transforms.py +1 -1
- cumulusci/core/source_transforms/transforms.py +1 -1
- cumulusci/core/tasks.py +13 -2
- cumulusci/core/tests/test_flowrunner.py +100 -0
- cumulusci/core/tests/test_tasks.py +65 -0
- cumulusci/core/utils.py +3 -1
- cumulusci/core/versions.py +1 -1
- cumulusci/cumulusci.yml +73 -1
- cumulusci/oauth/client.py +1 -1
- cumulusci/plugins/plugin_base.py +5 -3
- cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
- cumulusci/salesforce_api/rest_deploy.py +1 -1
- cumulusci/schema/cumulusci.jsonschema.json +69 -0
- cumulusci/tasks/apex/anon.py +1 -1
- cumulusci/tasks/apex/testrunner.py +421 -144
- cumulusci/tasks/apex/tests/test_apex_tasks.py +917 -1
- cumulusci/tasks/bulkdata/extract.py +0 -1
- cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +1 -1
- cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +1 -1
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +1 -1
- cumulusci/tasks/bulkdata/generate_and_load_data.py +136 -12
- cumulusci/tasks/bulkdata/mapping_parser.py +139 -44
- cumulusci/tasks/bulkdata/select_utils.py +1 -1
- cumulusci/tasks/bulkdata/snowfakery.py +100 -25
- cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +159 -0
- cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
- cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +763 -1
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +46 -0
- cumulusci/tasks/bulkdata/tests/test_snowfakery.py +133 -0
- cumulusci/tasks/create_package_version.py +190 -16
- cumulusci/tasks/datadictionary.py +1 -1
- cumulusci/tasks/metadata_etl/__init__.py +2 -0
- cumulusci/tasks/metadata_etl/applications.py +256 -0
- cumulusci/tasks/metadata_etl/base.py +7 -3
- cumulusci/tasks/metadata_etl/layouts.py +1 -1
- cumulusci/tasks/metadata_etl/permissions.py +1 -1
- cumulusci/tasks/metadata_etl/remote_site_settings.py +2 -2
- cumulusci/tasks/metadata_etl/tests/test_applications.py +710 -0
- cumulusci/tasks/push/README.md +15 -17
- cumulusci/tasks/release_notes/README.md +13 -13
- cumulusci/tasks/release_notes/generator.py +13 -8
- cumulusci/tasks/robotframework/tests/test_robotframework.py +6 -1
- cumulusci/tasks/salesforce/Deploy.py +53 -2
- cumulusci/tasks/salesforce/SfPackageCommands.py +363 -0
- cumulusci/tasks/salesforce/__init__.py +1 -0
- cumulusci/tasks/salesforce/assign_ps_psg.py +448 -0
- cumulusci/tasks/salesforce/composite.py +1 -1
- cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
- cumulusci/tasks/salesforce/enable_prediction.py +5 -1
- cumulusci/tasks/salesforce/getPackageVersion.py +89 -0
- cumulusci/tasks/salesforce/insert_record.py +18 -19
- cumulusci/tasks/salesforce/sourcetracking.py +1 -1
- cumulusci/tasks/salesforce/tests/test_Deploy.py +316 -1
- cumulusci/tasks/salesforce/tests/test_SfPackageCommands.py +554 -0
- cumulusci/tasks/salesforce/tests/test_assign_ps_psg.py +1055 -0
- cumulusci/tasks/salesforce/tests/test_enable_prediction.py +4 -2
- cumulusci/tasks/salesforce/tests/test_getPackageVersion.py +651 -0
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
- cumulusci/tasks/salesforce/tests/test_update_external_auth_identity_provider.py +927 -0
- cumulusci/tasks/salesforce/tests/test_update_external_credential.py +1427 -0
- cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
- cumulusci/tasks/salesforce/tests/test_update_record.py +512 -0
- cumulusci/tasks/salesforce/update_dependencies.py +2 -2
- cumulusci/tasks/salesforce/update_external_auth_identity_provider.py +551 -0
- cumulusci/tasks/salesforce/update_external_credential.py +647 -0
- cumulusci/tasks/salesforce/update_named_credential.py +441 -0
- cumulusci/tasks/salesforce/update_profile.py +17 -13
- cumulusci/tasks/salesforce/update_record.py +217 -0
- cumulusci/tasks/salesforce/users/permsets.py +62 -5
- cumulusci/tasks/salesforce/users/tests/test_permsets.py +237 -11
- cumulusci/tasks/sfdmu/__init__.py +0 -0
- cumulusci/tasks/sfdmu/sfdmu.py +376 -0
- cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
- cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
- cumulusci/tasks/sfdmu/tests/test_sfdmu.py +1012 -0
- cumulusci/tasks/tests/test_create_package_version.py +716 -1
- cumulusci/tasks/tests/test_util.py +42 -0
- cumulusci/tasks/util.py +37 -1
- cumulusci/tasks/utility/copyContents.py +402 -0
- cumulusci/tasks/utility/credentialManager.py +302 -0
- cumulusci/tasks/utility/directoryRecreator.py +30 -0
- cumulusci/tasks/utility/env_management.py +1 -1
- cumulusci/tasks/utility/secretsToEnv.py +135 -0
- cumulusci/tasks/utility/tests/test_copyContents.py +1719 -0
- cumulusci/tasks/utility/tests/test_credentialManager.py +1150 -0
- cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
- cumulusci/tasks/utility/tests/test_secretsToEnv.py +1118 -0
- cumulusci/tests/test_integration_infrastructure.py +3 -1
- cumulusci/tests/test_utils.py +70 -6
- cumulusci/utils/__init__.py +54 -9
- cumulusci/utils/classutils.py +5 -2
- cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
- cumulusci/utils/options.py +23 -1
- cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +1 -1
- cumulusci/utils/yaml/cumulusci_yml.py +8 -3
- cumulusci/utils/yaml/model_parser.py +2 -2
- cumulusci/utils/yaml/tests/test_cumulusci_yml.py +1 -1
- cumulusci/utils/yaml/tests/test_model_parser.py +3 -3
- cumulusci/vcs/base.py +23 -15
- cumulusci/vcs/bootstrap.py +5 -4
- cumulusci/vcs/utils/list_modified_files.py +189 -0
- cumulusci/vcs/utils/tests/test_list_modified_files.py +588 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/METADATA +11 -10
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/RECORD +135 -104
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/WHEEL +1 -1
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
from cumulusci.tasks.metadata_etl.applications import AddProfileActionOverrides
|
|
2
|
+
from cumulusci.tasks.salesforce.tests.util import create_task
|
|
3
|
+
from cumulusci.utils.xml import metadata_tree
|
|
4
|
+
|
|
5
|
+
MD = "{%s}" % metadata_tree.METADATA_NAMESPACE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
APPLICATION_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
|
9
|
+
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
10
|
+
<brand>
|
|
11
|
+
<headerColor>#0070D2</headerColor>
|
|
12
|
+
<shouldOverrideOrgTheme>false</shouldOverrideOrgTheme>
|
|
13
|
+
</brand>
|
|
14
|
+
<description>Test Application</description>
|
|
15
|
+
<label>Test App</label>
|
|
16
|
+
<navType>Console</navType>
|
|
17
|
+
{profileActionOverrides}
|
|
18
|
+
<uiType>Lightning</uiType>
|
|
19
|
+
</CustomApplication>
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
PROFILE_ACTION_OVERRIDE = """ <profileActionOverrides>
|
|
23
|
+
<actionName>View</actionName>
|
|
24
|
+
<content>TestRecordPage</content>
|
|
25
|
+
<formFactor>Large</formFactor>
|
|
26
|
+
<pageOrSobjectType>Account</pageOrSobjectType>
|
|
27
|
+
<recordType>PersonAccount.User</recordType>
|
|
28
|
+
<type>Flexipage</type>
|
|
29
|
+
<profile>Admin</profile>
|
|
30
|
+
</profileActionOverrides>
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestAddProfileActionOverrides:
|
|
35
|
+
def test_adds_profile_action_override(self):
|
|
36
|
+
"""Test adding a new profileActionOverride"""
|
|
37
|
+
task = create_task(
|
|
38
|
+
AddProfileActionOverrides,
|
|
39
|
+
{
|
|
40
|
+
"managed": True,
|
|
41
|
+
"api_version": "47.0",
|
|
42
|
+
"applications": [
|
|
43
|
+
{
|
|
44
|
+
"name": "TestApp",
|
|
45
|
+
"overrides": [
|
|
46
|
+
{
|
|
47
|
+
"action_name": "Edit",
|
|
48
|
+
"content": "CustomEditPage",
|
|
49
|
+
"form_factor": "Large",
|
|
50
|
+
"page_or_sobject_type": "Contact",
|
|
51
|
+
"record_type": "Contact.Business",
|
|
52
|
+
"type": "Flexipage",
|
|
53
|
+
"profile": "StandardUser",
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
tree = metadata_tree.fromstring(
|
|
62
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
63
|
+
)
|
|
64
|
+
element = tree._element
|
|
65
|
+
|
|
66
|
+
# Verify no existing override
|
|
67
|
+
assert len(element.findall(f".//{MD}profileActionOverrides")) == 0
|
|
68
|
+
|
|
69
|
+
result = task._transform_entity(tree, "TestApp")
|
|
70
|
+
|
|
71
|
+
# Verify override was added
|
|
72
|
+
assert len(result._element.findall(f".//{MD}profileActionOverrides")) == 1
|
|
73
|
+
|
|
74
|
+
override = result._element.find(f".//{MD}profileActionOverrides")
|
|
75
|
+
assert override.find(f"{MD}actionName").text == "Edit"
|
|
76
|
+
assert override.find(f"{MD}content").text == "CustomEditPage"
|
|
77
|
+
assert override.find(f"{MD}formFactor").text == "Large"
|
|
78
|
+
assert override.find(f"{MD}pageOrSobjectType").text == "Contact"
|
|
79
|
+
assert override.find(f"{MD}recordType").text == "Contact.Business"
|
|
80
|
+
assert override.find(f"{MD}type").text == "Flexipage"
|
|
81
|
+
assert override.find(f"{MD}profile").text == "StandardUser"
|
|
82
|
+
|
|
83
|
+
def test_adds_multiple_profile_action_overrides(self):
|
|
84
|
+
"""Test adding multiple profileActionOverrides to a single application"""
|
|
85
|
+
task = create_task(
|
|
86
|
+
AddProfileActionOverrides,
|
|
87
|
+
{
|
|
88
|
+
"managed": True,
|
|
89
|
+
"api_version": "47.0",
|
|
90
|
+
"applications": [
|
|
91
|
+
{
|
|
92
|
+
"name": "TestApp",
|
|
93
|
+
"overrides": [
|
|
94
|
+
{
|
|
95
|
+
"action_name": "View",
|
|
96
|
+
"content": "AccountViewPage",
|
|
97
|
+
"form_factor": "Large",
|
|
98
|
+
"page_or_sobject_type": "Account",
|
|
99
|
+
"record_type": "PersonAccount.User",
|
|
100
|
+
"type": "Flexipage",
|
|
101
|
+
"profile": "Admin",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"action_name": "Edit",
|
|
105
|
+
"content": "ContactEditPage",
|
|
106
|
+
"form_factor": "Small",
|
|
107
|
+
"page_or_sobject_type": "Contact",
|
|
108
|
+
"record_type": None,
|
|
109
|
+
"type": "Visualforce",
|
|
110
|
+
"profile": "StandardUser",
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
tree = metadata_tree.fromstring(
|
|
119
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
result = task._transform_entity(tree, "TestApp")
|
|
123
|
+
|
|
124
|
+
# Verify both overrides were added
|
|
125
|
+
overrides = result._element.findall(f".//{MD}profileActionOverrides")
|
|
126
|
+
assert len(overrides) == 2
|
|
127
|
+
|
|
128
|
+
# Check first override
|
|
129
|
+
override1 = overrides[0]
|
|
130
|
+
assert override1.find(f"{MD}actionName").text == "View"
|
|
131
|
+
assert override1.find(f"{MD}content").text == "AccountViewPage"
|
|
132
|
+
assert override1.find(f"{MD}profile").text == "Admin"
|
|
133
|
+
|
|
134
|
+
# Check second override
|
|
135
|
+
override2 = overrides[1]
|
|
136
|
+
assert override2.find(f"{MD}actionName").text == "Edit"
|
|
137
|
+
assert override2.find(f"{MD}content").text == "ContactEditPage"
|
|
138
|
+
assert override2.find(f"{MD}profile").text == "StandardUser"
|
|
139
|
+
# Verify recordType is not present when None
|
|
140
|
+
assert override2.find(f"{MD}recordType") is None
|
|
141
|
+
|
|
142
|
+
def test_adds_multiple_applications(self):
|
|
143
|
+
"""Test adding overrides to multiple applications"""
|
|
144
|
+
task = create_task(
|
|
145
|
+
AddProfileActionOverrides,
|
|
146
|
+
{
|
|
147
|
+
"managed": True,
|
|
148
|
+
"api_version": "47.0",
|
|
149
|
+
"applications": [
|
|
150
|
+
{
|
|
151
|
+
"name": "TestApp",
|
|
152
|
+
"overrides": [
|
|
153
|
+
{
|
|
154
|
+
"action_name": "View",
|
|
155
|
+
"content": "AccountViewPage",
|
|
156
|
+
"form_factor": "Large",
|
|
157
|
+
"page_or_sobject_type": "Account",
|
|
158
|
+
"record_type": None,
|
|
159
|
+
"type": "Flexipage",
|
|
160
|
+
"profile": "Admin",
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "SecondApp",
|
|
166
|
+
"overrides": [
|
|
167
|
+
{
|
|
168
|
+
"action_name": "Edit",
|
|
169
|
+
"content": "ContactEditPage",
|
|
170
|
+
"form_factor": "Large",
|
|
171
|
+
"page_or_sobject_type": "Contact",
|
|
172
|
+
"record_type": None,
|
|
173
|
+
"type": "Flexipage",
|
|
174
|
+
"profile": "StandardUser",
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Verify api_names contains both applications
|
|
183
|
+
assert "TestApp" in task.api_names
|
|
184
|
+
assert "SecondApp" in task.api_names
|
|
185
|
+
assert len(task.api_names) == 2
|
|
186
|
+
|
|
187
|
+
# Test first application
|
|
188
|
+
tree1 = metadata_tree.fromstring(
|
|
189
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
190
|
+
)
|
|
191
|
+
result1 = task._transform_entity(tree1, "TestApp")
|
|
192
|
+
overrides1 = result1._element.findall(f".//{MD}profileActionOverrides")
|
|
193
|
+
assert len(overrides1) == 1
|
|
194
|
+
assert overrides1[0].find(f"{MD}pageOrSobjectType").text == "Account"
|
|
195
|
+
|
|
196
|
+
# Test second application
|
|
197
|
+
tree2 = metadata_tree.fromstring(
|
|
198
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
199
|
+
)
|
|
200
|
+
result2 = task._transform_entity(tree2, "SecondApp")
|
|
201
|
+
overrides2 = result2._element.findall(f".//{MD}profileActionOverrides")
|
|
202
|
+
assert len(overrides2) == 1
|
|
203
|
+
assert overrides2[0].find(f"{MD}pageOrSobjectType").text == "Contact"
|
|
204
|
+
|
|
205
|
+
def test_updates_existing_profile_action_override(self):
|
|
206
|
+
"""Test updating an existing profileActionOverride"""
|
|
207
|
+
task = create_task(
|
|
208
|
+
AddProfileActionOverrides,
|
|
209
|
+
{
|
|
210
|
+
"managed": True,
|
|
211
|
+
"api_version": "47.0",
|
|
212
|
+
"applications": [
|
|
213
|
+
{
|
|
214
|
+
"name": "TestApp",
|
|
215
|
+
"overrides": [
|
|
216
|
+
{
|
|
217
|
+
"action_name": "View",
|
|
218
|
+
"content": "UpdatedRecordPage",
|
|
219
|
+
"form_factor": "Small",
|
|
220
|
+
"page_or_sobject_type": "Account",
|
|
221
|
+
"record_type": "PersonAccount.User",
|
|
222
|
+
"type": "LightningComponent",
|
|
223
|
+
"profile": "Admin",
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
tree = metadata_tree.fromstring(
|
|
232
|
+
APPLICATION_XML.format(
|
|
233
|
+
profileActionOverrides=PROFILE_ACTION_OVERRIDE
|
|
234
|
+
).encode("utf-8")
|
|
235
|
+
)
|
|
236
|
+
element = tree._element
|
|
237
|
+
|
|
238
|
+
# Verify existing override
|
|
239
|
+
assert len(element.findall(f".//{MD}profileActionOverrides")) == 1
|
|
240
|
+
original = element.find(f".//{MD}profileActionOverrides")
|
|
241
|
+
assert original.find(f"{MD}content").text == "TestRecordPage"
|
|
242
|
+
assert original.find(f"{MD}formFactor").text == "Large"
|
|
243
|
+
|
|
244
|
+
result = task._transform_entity(tree, "TestApp")
|
|
245
|
+
|
|
246
|
+
# Verify still only one override (updated, not added)
|
|
247
|
+
overrides = result._element.findall(f".//{MD}profileActionOverrides")
|
|
248
|
+
assert len(overrides) == 1
|
|
249
|
+
|
|
250
|
+
# Verify override was updated
|
|
251
|
+
override = overrides[0]
|
|
252
|
+
assert override.find(f"{MD}actionName").text == "View"
|
|
253
|
+
assert override.find(f"{MD}content").text == "UpdatedRecordPage"
|
|
254
|
+
assert override.find(f"{MD}formFactor").text == "Small"
|
|
255
|
+
assert override.find(f"{MD}type").text == "LightningComponent"
|
|
256
|
+
assert override.find(f"{MD}profile").text == "Admin"
|
|
257
|
+
|
|
258
|
+
def test_adds_override_when_different_profile(self):
|
|
259
|
+
"""Test that overrides with different profiles are treated as distinct"""
|
|
260
|
+
task = create_task(
|
|
261
|
+
AddProfileActionOverrides,
|
|
262
|
+
{
|
|
263
|
+
"managed": True,
|
|
264
|
+
"api_version": "47.0",
|
|
265
|
+
"applications": [
|
|
266
|
+
{
|
|
267
|
+
"name": "TestApp",
|
|
268
|
+
"overrides": [
|
|
269
|
+
{
|
|
270
|
+
"action_name": "View",
|
|
271
|
+
"content": "StandardUserRecordPage",
|
|
272
|
+
"form_factor": "Large",
|
|
273
|
+
"page_or_sobject_type": "Account",
|
|
274
|
+
"record_type": "PersonAccount.User",
|
|
275
|
+
"type": "Flexipage",
|
|
276
|
+
"profile": "StandardUser",
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
tree = metadata_tree.fromstring(
|
|
285
|
+
APPLICATION_XML.format(
|
|
286
|
+
profileActionOverrides=PROFILE_ACTION_OVERRIDE
|
|
287
|
+
).encode("utf-8")
|
|
288
|
+
)
|
|
289
|
+
element = tree._element
|
|
290
|
+
|
|
291
|
+
# Verify one existing override for Admin profile
|
|
292
|
+
assert len(element.findall(f".//{MD}profileActionOverrides")) == 1
|
|
293
|
+
|
|
294
|
+
result = task._transform_entity(tree, "TestApp")
|
|
295
|
+
|
|
296
|
+
# Verify two overrides now (original + new for different profile)
|
|
297
|
+
overrides = result._element.findall(f".//{MD}profileActionOverrides")
|
|
298
|
+
assert len(overrides) == 2
|
|
299
|
+
|
|
300
|
+
# Verify both profiles are present
|
|
301
|
+
profiles = [o.find(f"{MD}profile").text for o in overrides]
|
|
302
|
+
assert "Admin" in profiles
|
|
303
|
+
assert "StandardUser" in profiles
|
|
304
|
+
|
|
305
|
+
def test_namespace_injection_in_overrides(self):
|
|
306
|
+
"""Test that namespace injection works for override fields"""
|
|
307
|
+
task = create_task(
|
|
308
|
+
AddProfileActionOverrides,
|
|
309
|
+
{
|
|
310
|
+
"managed": True,
|
|
311
|
+
"api_version": "47.0",
|
|
312
|
+
"applications": [
|
|
313
|
+
{
|
|
314
|
+
"name": "TestApp",
|
|
315
|
+
"overrides": [
|
|
316
|
+
{
|
|
317
|
+
"action_name": "View",
|
|
318
|
+
"content": "%%%NAMESPACED_ORG%%%CustomPage",
|
|
319
|
+
"form_factor": "Large",
|
|
320
|
+
"page_or_sobject_type": "%%%NAMESPACE%%%CustomObject__c",
|
|
321
|
+
"record_type": "%%%NAMESPACE%%%CustomObject__c.%%%NAMESPACE%%%CustomRecordType",
|
|
322
|
+
"type": "Flexipage",
|
|
323
|
+
"profile": "Admin",
|
|
324
|
+
}
|
|
325
|
+
],
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
},
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
tree = metadata_tree.fromstring(
|
|
332
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
result = task._transform_entity(tree, "TestApp")
|
|
336
|
+
|
|
337
|
+
override = result._element.find(f".//{MD}profileActionOverrides")
|
|
338
|
+
# Namespace tokens should be processed (even if empty in test environment)
|
|
339
|
+
assert override.find(f"{MD}content") is not None
|
|
340
|
+
assert override.find(f"{MD}pageOrSobjectType") is not None
|
|
341
|
+
assert override.find(f"{MD}recordType") is not None
|
|
342
|
+
|
|
343
|
+
def test_namespace_injection_in_application_name(self):
|
|
344
|
+
"""Test that namespace injection works for application names"""
|
|
345
|
+
task = create_task(
|
|
346
|
+
AddProfileActionOverrides,
|
|
347
|
+
{
|
|
348
|
+
"managed": True,
|
|
349
|
+
"api_version": "47.0",
|
|
350
|
+
"applications": [
|
|
351
|
+
{
|
|
352
|
+
"name": "%%%NAMESPACE%%%TestApp",
|
|
353
|
+
"overrides": [
|
|
354
|
+
{
|
|
355
|
+
"action_name": "View",
|
|
356
|
+
"content": "CustomPage",
|
|
357
|
+
"form_factor": "Large",
|
|
358
|
+
"page_or_sobject_type": "Account",
|
|
359
|
+
"record_type": None,
|
|
360
|
+
"type": "Flexipage",
|
|
361
|
+
"profile": "Admin",
|
|
362
|
+
}
|
|
363
|
+
],
|
|
364
|
+
}
|
|
365
|
+
],
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Verify namespace token is processed in api_names
|
|
370
|
+
# In test environment without namespace, it should be empty string
|
|
371
|
+
assert len(task.api_names) == 1
|
|
372
|
+
|
|
373
|
+
def test_override_without_record_type(self):
|
|
374
|
+
"""Test adding override without recordType"""
|
|
375
|
+
task = create_task(
|
|
376
|
+
AddProfileActionOverrides,
|
|
377
|
+
{
|
|
378
|
+
"managed": True,
|
|
379
|
+
"api_version": "47.0",
|
|
380
|
+
"applications": [
|
|
381
|
+
{
|
|
382
|
+
"name": "TestApp",
|
|
383
|
+
"overrides": [
|
|
384
|
+
{
|
|
385
|
+
"action_name": "New",
|
|
386
|
+
"content": "NewContactPage",
|
|
387
|
+
"form_factor": "Large",
|
|
388
|
+
"page_or_sobject_type": "Contact",
|
|
389
|
+
"record_type": None,
|
|
390
|
+
"type": "Flexipage",
|
|
391
|
+
"profile": "Admin",
|
|
392
|
+
}
|
|
393
|
+
],
|
|
394
|
+
}
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
tree = metadata_tree.fromstring(
|
|
400
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
result = task._transform_entity(tree, "TestApp")
|
|
404
|
+
|
|
405
|
+
override = result._element.find(f".//{MD}profileActionOverrides")
|
|
406
|
+
assert override.find(f"{MD}actionName").text == "New"
|
|
407
|
+
assert override.find(f"{MD}pageOrSobjectType").text == "Contact"
|
|
408
|
+
# Verify recordType element is not present
|
|
409
|
+
assert override.find(f"{MD}recordType") is None
|
|
410
|
+
|
|
411
|
+
def test_skips_override_when_no_overrides_provided(self):
|
|
412
|
+
"""Test that task returns None when no overrides are provided"""
|
|
413
|
+
task = create_task(
|
|
414
|
+
AddProfileActionOverrides,
|
|
415
|
+
{
|
|
416
|
+
"managed": True,
|
|
417
|
+
"api_version": "47.0",
|
|
418
|
+
"applications": [
|
|
419
|
+
{
|
|
420
|
+
"name": "TestApp",
|
|
421
|
+
"overrides": [],
|
|
422
|
+
}
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
tree = metadata_tree.fromstring(
|
|
428
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
result = task._transform_entity(tree, "TestApp")
|
|
432
|
+
|
|
433
|
+
# Task should return metadata even with empty overrides
|
|
434
|
+
# but no overrides should be added
|
|
435
|
+
assert result is not None
|
|
436
|
+
assert len(result._element.findall(f".//{MD}profileActionOverrides")) == 0
|
|
437
|
+
|
|
438
|
+
def test_skips_override_when_no_applications_provided(self):
|
|
439
|
+
"""Test that task returns None when no applications are provided"""
|
|
440
|
+
task = create_task(
|
|
441
|
+
AddProfileActionOverrides,
|
|
442
|
+
{
|
|
443
|
+
"managed": True,
|
|
444
|
+
"api_version": "47.0",
|
|
445
|
+
"applications": [],
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
tree = metadata_tree.fromstring(
|
|
450
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
result = task._transform_entity(tree, "TestApp")
|
|
454
|
+
|
|
455
|
+
# Task should return None when no applications configured
|
|
456
|
+
assert result is None
|
|
457
|
+
|
|
458
|
+
def test_skips_application_when_name_does_not_match(self):
|
|
459
|
+
"""Test that overrides are only applied to matching applications"""
|
|
460
|
+
task = create_task(
|
|
461
|
+
AddProfileActionOverrides,
|
|
462
|
+
{
|
|
463
|
+
"managed": True,
|
|
464
|
+
"api_version": "47.0",
|
|
465
|
+
"applications": [
|
|
466
|
+
{
|
|
467
|
+
"name": "DifferentApp",
|
|
468
|
+
"overrides": [
|
|
469
|
+
{
|
|
470
|
+
"action_name": "View",
|
|
471
|
+
"content": "CustomPage",
|
|
472
|
+
"form_factor": "Large",
|
|
473
|
+
"page_or_sobject_type": "Account",
|
|
474
|
+
"record_type": None,
|
|
475
|
+
"type": "Flexipage",
|
|
476
|
+
"profile": "Admin",
|
|
477
|
+
}
|
|
478
|
+
],
|
|
479
|
+
}
|
|
480
|
+
],
|
|
481
|
+
},
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
tree = metadata_tree.fromstring(
|
|
485
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
result = task._transform_entity(tree, "TestApp")
|
|
489
|
+
|
|
490
|
+
# No overrides should be added since application name doesn't match
|
|
491
|
+
assert result is not None
|
|
492
|
+
assert len(result._element.findall(f".//{MD}profileActionOverrides")) == 0
|
|
493
|
+
|
|
494
|
+
def test_different_record_type_none_creates_separate_override(self):
|
|
495
|
+
"""Test that overrides with None recordType vs specific recordType are treated as distinct"""
|
|
496
|
+
task = create_task(
|
|
497
|
+
AddProfileActionOverrides,
|
|
498
|
+
{
|
|
499
|
+
"managed": True,
|
|
500
|
+
"api_version": "47.0",
|
|
501
|
+
"applications": [
|
|
502
|
+
{
|
|
503
|
+
"name": "TestApp",
|
|
504
|
+
"overrides": [
|
|
505
|
+
{
|
|
506
|
+
"action_name": "View",
|
|
507
|
+
"content": "GenericAccountPage",
|
|
508
|
+
"form_factor": "Large",
|
|
509
|
+
"page_or_sobject_type": "Account",
|
|
510
|
+
"record_type": None, # No specific recordType
|
|
511
|
+
"type": "Flexipage",
|
|
512
|
+
"profile": "Admin",
|
|
513
|
+
}
|
|
514
|
+
],
|
|
515
|
+
}
|
|
516
|
+
],
|
|
517
|
+
},
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
tree = metadata_tree.fromstring(
|
|
521
|
+
APPLICATION_XML.format(
|
|
522
|
+
profileActionOverrides=PROFILE_ACTION_OVERRIDE
|
|
523
|
+
).encode("utf-8")
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
# Original has recordType "PersonAccount.User"
|
|
527
|
+
original = tree._element.find(f".//{MD}profileActionOverrides")
|
|
528
|
+
assert original.find(f"{MD}recordType").text == "PersonAccount.User"
|
|
529
|
+
|
|
530
|
+
result = task._transform_entity(tree, "TestApp")
|
|
531
|
+
|
|
532
|
+
# Should have two overrides now (one with recordType, one without)
|
|
533
|
+
overrides = result._element.findall(f".//{MD}profileActionOverrides")
|
|
534
|
+
assert len(overrides) == 2
|
|
535
|
+
|
|
536
|
+
# Check that we have one with recordType and one without
|
|
537
|
+
overrides_with_record_type = [
|
|
538
|
+
o for o in overrides if o.find(f"{MD}recordType") is not None
|
|
539
|
+
]
|
|
540
|
+
overrides_without_record_type = [
|
|
541
|
+
o for o in overrides if o.find(f"{MD}recordType") is None
|
|
542
|
+
]
|
|
543
|
+
assert len(overrides_with_record_type) == 1
|
|
544
|
+
assert len(overrides_without_record_type) == 1
|
|
545
|
+
|
|
546
|
+
def test_different_record_types_create_separate_overrides(self):
|
|
547
|
+
"""Test that overrides with different recordTypes are treated as distinct"""
|
|
548
|
+
task = create_task(
|
|
549
|
+
AddProfileActionOverrides,
|
|
550
|
+
{
|
|
551
|
+
"managed": True,
|
|
552
|
+
"api_version": "47.0",
|
|
553
|
+
"applications": [
|
|
554
|
+
{
|
|
555
|
+
"name": "TestApp",
|
|
556
|
+
"overrides": [
|
|
557
|
+
{
|
|
558
|
+
"action_name": "View",
|
|
559
|
+
"content": "BusinessAccountPage",
|
|
560
|
+
"form_factor": "Large",
|
|
561
|
+
"page_or_sobject_type": "Account",
|
|
562
|
+
"record_type": "Account.Business",
|
|
563
|
+
"type": "Flexipage",
|
|
564
|
+
"profile": "Admin",
|
|
565
|
+
}
|
|
566
|
+
],
|
|
567
|
+
}
|
|
568
|
+
],
|
|
569
|
+
},
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
tree = metadata_tree.fromstring(
|
|
573
|
+
APPLICATION_XML.format(
|
|
574
|
+
profileActionOverrides=PROFILE_ACTION_OVERRIDE
|
|
575
|
+
).encode("utf-8")
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# Original has PersonAccount.User recordType
|
|
579
|
+
original = tree._element.find(f".//{MD}profileActionOverrides")
|
|
580
|
+
assert original.find(f"{MD}recordType").text == "PersonAccount.User"
|
|
581
|
+
|
|
582
|
+
result = task._transform_entity(tree, "TestApp")
|
|
583
|
+
|
|
584
|
+
# Should have two overrides now (different recordTypes)
|
|
585
|
+
overrides = result._element.findall(f".//{MD}profileActionOverrides")
|
|
586
|
+
assert len(overrides) == 2
|
|
587
|
+
|
|
588
|
+
record_types = [
|
|
589
|
+
o.find(f"{MD}recordType").text
|
|
590
|
+
for o in overrides
|
|
591
|
+
if o.find(f"{MD}recordType") is not None
|
|
592
|
+
]
|
|
593
|
+
assert "PersonAccount.User" in record_types
|
|
594
|
+
assert "Account.Business" in record_types
|
|
595
|
+
|
|
596
|
+
def test_all_override_properties_are_set(self):
|
|
597
|
+
"""Test that all properties of an override are correctly set"""
|
|
598
|
+
task = create_task(
|
|
599
|
+
AddProfileActionOverrides,
|
|
600
|
+
{
|
|
601
|
+
"managed": True,
|
|
602
|
+
"api_version": "47.0",
|
|
603
|
+
"applications": [
|
|
604
|
+
{
|
|
605
|
+
"name": "TestApp",
|
|
606
|
+
"overrides": [
|
|
607
|
+
{
|
|
608
|
+
"action_name": "Clone",
|
|
609
|
+
"content": "CustomClonePage",
|
|
610
|
+
"form_factor": "Small",
|
|
611
|
+
"page_or_sobject_type": "Lead",
|
|
612
|
+
"record_type": "Lead.Enterprise",
|
|
613
|
+
"type": "LightningComponent",
|
|
614
|
+
"profile": "SalesUser",
|
|
615
|
+
}
|
|
616
|
+
],
|
|
617
|
+
}
|
|
618
|
+
],
|
|
619
|
+
},
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
tree = metadata_tree.fromstring(
|
|
623
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
result = task._transform_entity(tree, "TestApp")
|
|
627
|
+
|
|
628
|
+
override = result._element.find(f".//{MD}profileActionOverrides")
|
|
629
|
+
|
|
630
|
+
# Verify all properties are correctly set
|
|
631
|
+
assert override.find(f"{MD}actionName").text == "Clone"
|
|
632
|
+
assert override.find(f"{MD}content").text == "CustomClonePage"
|
|
633
|
+
assert override.find(f"{MD}formFactor").text == "Small"
|
|
634
|
+
assert override.find(f"{MD}pageOrSobjectType").text == "Lead"
|
|
635
|
+
assert override.find(f"{MD}recordType").text == "Lead.Enterprise"
|
|
636
|
+
assert override.find(f"{MD}type").text == "LightningComponent"
|
|
637
|
+
assert override.find(f"{MD}profile").text == "SalesUser"
|
|
638
|
+
|
|
639
|
+
def test_complex_scenario_multiple_apps_and_overrides(self):
|
|
640
|
+
"""Test complex scenario with multiple applications and multiple overrides per app"""
|
|
641
|
+
task = create_task(
|
|
642
|
+
AddProfileActionOverrides,
|
|
643
|
+
{
|
|
644
|
+
"managed": True,
|
|
645
|
+
"api_version": "47.0",
|
|
646
|
+
"applications": [
|
|
647
|
+
{
|
|
648
|
+
"name": "AdminConsole",
|
|
649
|
+
"overrides": [
|
|
650
|
+
{
|
|
651
|
+
"action_name": "View",
|
|
652
|
+
"content": "AdminAccountView",
|
|
653
|
+
"form_factor": "Large",
|
|
654
|
+
"page_or_sobject_type": "Account",
|
|
655
|
+
"record_type": None,
|
|
656
|
+
"type": "Flexipage",
|
|
657
|
+
"profile": "Admin",
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"action_name": "Edit",
|
|
661
|
+
"content": "AdminAccountEdit",
|
|
662
|
+
"form_factor": "Large",
|
|
663
|
+
"page_or_sobject_type": "Account",
|
|
664
|
+
"record_type": None,
|
|
665
|
+
"type": "Flexipage",
|
|
666
|
+
"profile": "Admin",
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
"name": "SalesConsole",
|
|
672
|
+
"overrides": [
|
|
673
|
+
{
|
|
674
|
+
"action_name": "View",
|
|
675
|
+
"content": "SalesContactView",
|
|
676
|
+
"form_factor": "Large",
|
|
677
|
+
"page_or_sobject_type": "Contact",
|
|
678
|
+
"record_type": None,
|
|
679
|
+
"type": "Flexipage",
|
|
680
|
+
"profile": "SalesUser",
|
|
681
|
+
}
|
|
682
|
+
],
|
|
683
|
+
},
|
|
684
|
+
],
|
|
685
|
+
},
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
# Verify both apps in api_names
|
|
689
|
+
assert "AdminConsole" in task.api_names
|
|
690
|
+
assert "SalesConsole" in task.api_names
|
|
691
|
+
|
|
692
|
+
# Test AdminConsole
|
|
693
|
+
tree1 = metadata_tree.fromstring(
|
|
694
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
695
|
+
)
|
|
696
|
+
result1 = task._transform_entity(tree1, "AdminConsole")
|
|
697
|
+
overrides1 = result1._element.findall(f".//{MD}profileActionOverrides")
|
|
698
|
+
assert len(overrides1) == 2
|
|
699
|
+
actions1 = [o.find(f"{MD}actionName").text for o in overrides1]
|
|
700
|
+
assert "View" in actions1
|
|
701
|
+
assert "Edit" in actions1
|
|
702
|
+
|
|
703
|
+
# Test SalesConsole
|
|
704
|
+
tree2 = metadata_tree.fromstring(
|
|
705
|
+
APPLICATION_XML.format(profileActionOverrides="").encode("utf-8")
|
|
706
|
+
)
|
|
707
|
+
result2 = task._transform_entity(tree2, "SalesConsole")
|
|
708
|
+
overrides2 = result2._element.findall(f".//{MD}profileActionOverrides")
|
|
709
|
+
assert len(overrides2) == 1
|
|
710
|
+
assert overrides2[0].find(f"{MD}pageOrSobjectType").text == "Contact"
|