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,512 @@
|
|
|
1
|
+
from unittest import mock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from cumulusci.core.exceptions import SalesforceException
|
|
6
|
+
from cumulusci.tasks.salesforce.update_record import UpdateRecord
|
|
7
|
+
|
|
8
|
+
from .util import create_task
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestUpdateRecord:
|
|
12
|
+
def test_run_task_with_record_id(self):
|
|
13
|
+
"""Test updating a single record by ID"""
|
|
14
|
+
task = create_task(
|
|
15
|
+
UpdateRecord,
|
|
16
|
+
{
|
|
17
|
+
"object": "Account",
|
|
18
|
+
"record_id": "001xx000003DGbXXXX",
|
|
19
|
+
"values": "Name:UpdatedName,Status__c:Active",
|
|
20
|
+
},
|
|
21
|
+
)
|
|
22
|
+
task._init_task()
|
|
23
|
+
task.sf = mock.Mock()
|
|
24
|
+
task.api = task.sf # Point api to mocked sf
|
|
25
|
+
task.sf.Account.update.return_value = 204
|
|
26
|
+
|
|
27
|
+
task._run_task()
|
|
28
|
+
task.sf.Account.update.assert_called_once_with(
|
|
29
|
+
"001xx000003DGbXXXX", {"Name": "UpdatedName", "Status__c": "Active"}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def test_run_task_with_record_id_dict_values(self):
|
|
33
|
+
"""Test updating with dict values instead of string"""
|
|
34
|
+
task = create_task(
|
|
35
|
+
UpdateRecord,
|
|
36
|
+
{
|
|
37
|
+
"object": "Account",
|
|
38
|
+
"record_id": "001xx000003DGbXXXX",
|
|
39
|
+
"values": {"Name": "UpdatedName", "Active__c": True},
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
task._init_task()
|
|
43
|
+
task.sf = mock.Mock()
|
|
44
|
+
task.api = task.sf
|
|
45
|
+
task.sf.Account.update.return_value = 204
|
|
46
|
+
|
|
47
|
+
task._run_task()
|
|
48
|
+
task.sf.Account.update.assert_called_once_with(
|
|
49
|
+
"001xx000003DGbXXXX", {"Name": "UpdatedName", "Active__c": True}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def test_run_task_with_tooling_api(self):
|
|
53
|
+
"""Test using Tooling API"""
|
|
54
|
+
task = create_task(
|
|
55
|
+
UpdateRecord,
|
|
56
|
+
{
|
|
57
|
+
"object": "PermissionSet",
|
|
58
|
+
"record_id": "0PS3D000000MKTqWAO",
|
|
59
|
+
"values": "Label:UpdatedLabel",
|
|
60
|
+
"tooling": True,
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
task._init_task()
|
|
64
|
+
task.tooling = mock.Mock()
|
|
65
|
+
task.api = task.tooling
|
|
66
|
+
task.tooling.PermissionSet.update.return_value = 204
|
|
67
|
+
|
|
68
|
+
task._run_task()
|
|
69
|
+
task.tooling.PermissionSet.update.assert_called_once_with(
|
|
70
|
+
"0PS3D000000MKTqWAO", {"Label": "UpdatedLabel"}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def test_run_task_with_where_clause_single_record(self):
|
|
74
|
+
"""Test updating a single record found by query"""
|
|
75
|
+
task = create_task(
|
|
76
|
+
UpdateRecord,
|
|
77
|
+
{
|
|
78
|
+
"object": "Account",
|
|
79
|
+
"where": "Name:TestAccount,Status__c:Draft",
|
|
80
|
+
"values": "Status__c:Active",
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
task._init_task()
|
|
84
|
+
task.sf = mock.Mock()
|
|
85
|
+
task.api = task.sf
|
|
86
|
+
task.sf.query.return_value = {
|
|
87
|
+
"records": [{"Id": "001xx000003DGbXXXX"}],
|
|
88
|
+
"totalSize": 1,
|
|
89
|
+
}
|
|
90
|
+
task.sf.Account.update.return_value = 204
|
|
91
|
+
|
|
92
|
+
task._run_task()
|
|
93
|
+
|
|
94
|
+
# Verify query was executed
|
|
95
|
+
task.sf.query.assert_called_once()
|
|
96
|
+
query_arg = task.sf.query.call_args[0][0]
|
|
97
|
+
assert "SELECT Id FROM Account WHERE" in query_arg
|
|
98
|
+
assert "Name = 'TestAccount'" in query_arg
|
|
99
|
+
assert "Status__c = 'Draft'" in query_arg
|
|
100
|
+
|
|
101
|
+
# Verify update was called
|
|
102
|
+
task.sf.Account.update.assert_called_once_with(
|
|
103
|
+
"001xx000003DGbXXXX", {"Status__c": "Active"}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def test_run_task_with_where_clause_multiple_records(self):
|
|
107
|
+
"""Test updating multiple records found by query using bulk API"""
|
|
108
|
+
task = create_task(
|
|
109
|
+
UpdateRecord,
|
|
110
|
+
{
|
|
111
|
+
"object": "Contact",
|
|
112
|
+
"where": "Status__c:Draft",
|
|
113
|
+
"values": "Status__c:Active",
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
task._init_task()
|
|
117
|
+
task.sf = mock.Mock()
|
|
118
|
+
task.api = task.sf
|
|
119
|
+
task.sf.query.return_value = {
|
|
120
|
+
"records": [
|
|
121
|
+
{"Id": "003xx000001Record1"},
|
|
122
|
+
{"Id": "003xx000001Record2"},
|
|
123
|
+
{"Id": "003xx000001Record3"},
|
|
124
|
+
],
|
|
125
|
+
"totalSize": 3,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Mock bulk API results
|
|
129
|
+
task.bulk = mock.Mock()
|
|
130
|
+
bulk_result = mock.Mock()
|
|
131
|
+
bulk_result.success = True
|
|
132
|
+
task.bulk.update.return_value = [bulk_result, bulk_result, bulk_result]
|
|
133
|
+
|
|
134
|
+
task._run_task()
|
|
135
|
+
|
|
136
|
+
# Verify bulk update was called with all records
|
|
137
|
+
task.bulk.update.assert_called_once()
|
|
138
|
+
call_args = task.bulk.update.call_args[0]
|
|
139
|
+
assert call_args[0] == "Contact"
|
|
140
|
+
assert len(call_args[1]) == 3
|
|
141
|
+
assert call_args[1][0] == {"Id": "003xx000001Record1", "Status__c": "Active"}
|
|
142
|
+
assert call_args[1][1] == {"Id": "003xx000001Record2", "Status__c": "Active"}
|
|
143
|
+
assert call_args[1][2] == {"Id": "003xx000001Record3", "Status__c": "Active"}
|
|
144
|
+
|
|
145
|
+
def test_run_task_no_records_found(self):
|
|
146
|
+
"""Test warning when no records are found"""
|
|
147
|
+
task = create_task(
|
|
148
|
+
UpdateRecord,
|
|
149
|
+
{
|
|
150
|
+
"object": "Account",
|
|
151
|
+
"where": "Name:NonExistentAccount",
|
|
152
|
+
"values": "Status__c:Active",
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
task._init_task()
|
|
156
|
+
task.sf = mock.Mock()
|
|
157
|
+
task.api = task.sf
|
|
158
|
+
task.sf.query.return_value = {"records": [], "totalSize": 0}
|
|
159
|
+
|
|
160
|
+
# Should not raise an exception, just log a warning
|
|
161
|
+
task._run_task()
|
|
162
|
+
|
|
163
|
+
# Verify no updates were attempted
|
|
164
|
+
task.sf.Account.update.assert_not_called()
|
|
165
|
+
|
|
166
|
+
def test_run_task_partial_failure_with_fail_on_error_true(self):
|
|
167
|
+
"""Test that partial failures raise exception when fail_on_error=True using bulk API"""
|
|
168
|
+
task = create_task(
|
|
169
|
+
UpdateRecord,
|
|
170
|
+
{
|
|
171
|
+
"object": "Account",
|
|
172
|
+
"where": "Status__c:Draft",
|
|
173
|
+
"values": "Status__c:Active",
|
|
174
|
+
"fail_on_error": True,
|
|
175
|
+
},
|
|
176
|
+
)
|
|
177
|
+
task._init_task()
|
|
178
|
+
task.sf = mock.Mock()
|
|
179
|
+
task.api = task.sf
|
|
180
|
+
task.sf.query.return_value = {
|
|
181
|
+
"records": [
|
|
182
|
+
{"Id": "001xx000001Record1"},
|
|
183
|
+
{"Id": "001xx000001Record2"},
|
|
184
|
+
],
|
|
185
|
+
"totalSize": 2,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Mock bulk API: first succeeds, second fails
|
|
189
|
+
task.bulk = mock.Mock()
|
|
190
|
+
success_result = mock.Mock()
|
|
191
|
+
success_result.success = True
|
|
192
|
+
failure_result = mock.Mock()
|
|
193
|
+
failure_result.success = False
|
|
194
|
+
failure_result.error = "Update failed"
|
|
195
|
+
task.bulk.update.return_value = [success_result, failure_result]
|
|
196
|
+
|
|
197
|
+
with pytest.raises(SalesforceException) as exc_info:
|
|
198
|
+
task._run_task()
|
|
199
|
+
|
|
200
|
+
assert "Failed to update 1 record(s)" in str(exc_info.value)
|
|
201
|
+
|
|
202
|
+
def test_run_task_partial_failure_with_fail_on_error_false(self):
|
|
203
|
+
"""Test that partial failures are logged when fail_on_error=False using bulk API"""
|
|
204
|
+
task = create_task(
|
|
205
|
+
UpdateRecord,
|
|
206
|
+
{
|
|
207
|
+
"object": "Account",
|
|
208
|
+
"where": "Status__c:Draft",
|
|
209
|
+
"values": "Status__c:Active",
|
|
210
|
+
"fail_on_error": False,
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
task._init_task()
|
|
214
|
+
task.sf = mock.Mock()
|
|
215
|
+
task.api = task.sf
|
|
216
|
+
task.sf.query.return_value = {
|
|
217
|
+
"records": [
|
|
218
|
+
{"Id": "001xx000001Record1"},
|
|
219
|
+
{"Id": "001xx000001Record2"},
|
|
220
|
+
],
|
|
221
|
+
"totalSize": 2,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Mock bulk API: first succeeds, second fails
|
|
225
|
+
task.bulk = mock.Mock()
|
|
226
|
+
success_result = mock.Mock()
|
|
227
|
+
success_result.success = True
|
|
228
|
+
failure_result = mock.Mock()
|
|
229
|
+
failure_result.success = False
|
|
230
|
+
failure_result.error = "Update failed"
|
|
231
|
+
task.bulk.update.return_value = [success_result, failure_result]
|
|
232
|
+
|
|
233
|
+
# Should not raise an exception
|
|
234
|
+
task._run_task()
|
|
235
|
+
|
|
236
|
+
# Verify bulk update was called
|
|
237
|
+
task.bulk.update.assert_called_once()
|
|
238
|
+
|
|
239
|
+
def test_run_task_update_by_id_with_fail_on_error_false(self):
|
|
240
|
+
"""Test that update by ID with error doesn't raise when fail_on_error=False"""
|
|
241
|
+
task = create_task(
|
|
242
|
+
UpdateRecord,
|
|
243
|
+
{
|
|
244
|
+
"object": "Account",
|
|
245
|
+
"record_id": "001xx000003DGbXXXX",
|
|
246
|
+
"values": "Name:UpdatedName",
|
|
247
|
+
"fail_on_error": False,
|
|
248
|
+
},
|
|
249
|
+
)
|
|
250
|
+
task._init_task()
|
|
251
|
+
task.sf = mock.Mock()
|
|
252
|
+
task.api = task.sf
|
|
253
|
+
task.sf.Account.update.side_effect = Exception("Update failed")
|
|
254
|
+
|
|
255
|
+
# Should not raise an exception
|
|
256
|
+
task._run_task()
|
|
257
|
+
|
|
258
|
+
# Verify update was attempted
|
|
259
|
+
task.sf.Account.update.assert_called_once()
|
|
260
|
+
|
|
261
|
+
def test_missing_record_id_and_where(self):
|
|
262
|
+
"""Test that missing both record_id and where raises exception"""
|
|
263
|
+
with pytest.raises(SalesforceException) as exc_info:
|
|
264
|
+
create_task(
|
|
265
|
+
UpdateRecord,
|
|
266
|
+
{
|
|
267
|
+
"object": "Account",
|
|
268
|
+
"values": "Name:UpdatedName",
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
assert "Either 'record_id' or 'where' option must be specified" in str(
|
|
272
|
+
exc_info.value
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def test_record_id_ignores_where(self):
|
|
276
|
+
"""Test that record_id takes precedence over where clause"""
|
|
277
|
+
task = create_task(
|
|
278
|
+
UpdateRecord,
|
|
279
|
+
{
|
|
280
|
+
"object": "Account",
|
|
281
|
+
"record_id": "001xx000003DGbXXXX",
|
|
282
|
+
"where": "Name:TestAccount",
|
|
283
|
+
"values": "Status__c:Active",
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
task._init_task()
|
|
287
|
+
task.sf = mock.Mock()
|
|
288
|
+
task.api = task.sf
|
|
289
|
+
task.sf.Account.update.return_value = 204
|
|
290
|
+
|
|
291
|
+
task._run_task()
|
|
292
|
+
|
|
293
|
+
# Verify query was NOT called
|
|
294
|
+
task.sf.query.assert_not_called()
|
|
295
|
+
|
|
296
|
+
# Verify direct update was called
|
|
297
|
+
task.sf.Account.update.assert_called_once_with(
|
|
298
|
+
"001xx000003DGbXXXX", {"Status__c": "Active"}
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def test_salesforce_error_update_by_id(self):
|
|
302
|
+
"""Test Salesforce error when updating by ID with fail_on_error=True"""
|
|
303
|
+
task = create_task(
|
|
304
|
+
UpdateRecord,
|
|
305
|
+
{
|
|
306
|
+
"object": "Account",
|
|
307
|
+
"record_id": "001xx000003DGbXXXX",
|
|
308
|
+
"values": "Name:UpdatedName",
|
|
309
|
+
},
|
|
310
|
+
)
|
|
311
|
+
task._init_task()
|
|
312
|
+
task.sf = mock.Mock()
|
|
313
|
+
task.api = task.sf
|
|
314
|
+
task.sf.Account.update.side_effect = Exception("Invalid field")
|
|
315
|
+
|
|
316
|
+
with pytest.raises(SalesforceException) as exc_info:
|
|
317
|
+
task._run_task()
|
|
318
|
+
|
|
319
|
+
assert "Error updating Account record" in str(exc_info.value)
|
|
320
|
+
|
|
321
|
+
def test_query_error(self):
|
|
322
|
+
"""Test error during query execution"""
|
|
323
|
+
task = create_task(
|
|
324
|
+
UpdateRecord,
|
|
325
|
+
{
|
|
326
|
+
"object": "Account",
|
|
327
|
+
"where": "Name:TestAccount",
|
|
328
|
+
"values": "Status__c:Active",
|
|
329
|
+
},
|
|
330
|
+
)
|
|
331
|
+
task._init_task()
|
|
332
|
+
task.sf = mock.Mock()
|
|
333
|
+
task.api = task.sf
|
|
334
|
+
task.sf.query.side_effect = Exception("Invalid query")
|
|
335
|
+
|
|
336
|
+
with pytest.raises(SalesforceException) as exc_info:
|
|
337
|
+
task._run_task()
|
|
338
|
+
|
|
339
|
+
assert "Error executing query" in str(exc_info.value)
|
|
340
|
+
|
|
341
|
+
def test_update_with_dict_response(self):
|
|
342
|
+
"""Test handling dict response from simple_salesforce"""
|
|
343
|
+
task = create_task(
|
|
344
|
+
UpdateRecord,
|
|
345
|
+
{
|
|
346
|
+
"object": "Account",
|
|
347
|
+
"record_id": "001xx000003DGbXXXX",
|
|
348
|
+
"values": "Name:UpdatedName",
|
|
349
|
+
},
|
|
350
|
+
)
|
|
351
|
+
task._init_task()
|
|
352
|
+
task.sf = mock.Mock()
|
|
353
|
+
task.api = task.sf
|
|
354
|
+
task.sf.Account.update.return_value = {"success": True}
|
|
355
|
+
|
|
356
|
+
task._run_task()
|
|
357
|
+
|
|
358
|
+
task.sf.Account.update.assert_called_once_with(
|
|
359
|
+
"001xx000003DGbXXXX", {"Name": "UpdatedName"}
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
def test_missing_values_and_transform_values(self):
|
|
363
|
+
"""Test that missing both values and transform_values raises exception"""
|
|
364
|
+
with pytest.raises(SalesforceException) as exc_info:
|
|
365
|
+
create_task(
|
|
366
|
+
UpdateRecord,
|
|
367
|
+
{
|
|
368
|
+
"object": "Account",
|
|
369
|
+
"record_id": "001xx000003DGbXXXX",
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
assert "Either 'values' or 'transform_values' option must be specified" in str(
|
|
373
|
+
exc_info.value
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def test_transform_values_with_env_vars(self):
|
|
377
|
+
"""Test transform_values extracts values from environment variables"""
|
|
378
|
+
import os
|
|
379
|
+
|
|
380
|
+
# Set environment variables
|
|
381
|
+
os.environ["TEST_ACCOUNT_NAME"] = "TestAccountFromEnv"
|
|
382
|
+
os.environ["TEST_STATUS"] = "ActiveFromEnv"
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
task = create_task(
|
|
386
|
+
UpdateRecord,
|
|
387
|
+
{
|
|
388
|
+
"object": "Account",
|
|
389
|
+
"record_id": "001xx000003DGbXXXX",
|
|
390
|
+
"transform_values": "Name:TEST_ACCOUNT_NAME,Status__c:TEST_STATUS",
|
|
391
|
+
},
|
|
392
|
+
)
|
|
393
|
+
task._init_task()
|
|
394
|
+
task.sf = mock.Mock()
|
|
395
|
+
task.api = task.sf
|
|
396
|
+
task.sf.Account.update.return_value = 204
|
|
397
|
+
|
|
398
|
+
task._run_task()
|
|
399
|
+
|
|
400
|
+
# Verify the update was called with environment variable values
|
|
401
|
+
task.sf.Account.update.assert_called_once_with(
|
|
402
|
+
"001xx000003DGbXXXX",
|
|
403
|
+
{"Name": "TestAccountFromEnv", "Status__c": "ActiveFromEnv"},
|
|
404
|
+
)
|
|
405
|
+
finally:
|
|
406
|
+
# Clean up environment variables
|
|
407
|
+
del os.environ["TEST_ACCOUNT_NAME"]
|
|
408
|
+
del os.environ["TEST_STATUS"]
|
|
409
|
+
|
|
410
|
+
def test_transform_values_with_missing_env_vars(self):
|
|
411
|
+
"""Test transform_values defaults to key name when env var doesn't exist"""
|
|
412
|
+
task = create_task(
|
|
413
|
+
UpdateRecord,
|
|
414
|
+
{
|
|
415
|
+
"object": "Account",
|
|
416
|
+
"record_id": "001xx000003DGbXXXX",
|
|
417
|
+
"transform_values": "Name:NONEXISTENT_VAR,Status__c:ANOTHER_MISSING_VAR",
|
|
418
|
+
},
|
|
419
|
+
)
|
|
420
|
+
task._init_task()
|
|
421
|
+
task.sf = mock.Mock()
|
|
422
|
+
task.api = task.sf
|
|
423
|
+
task.sf.Account.update.return_value = 204
|
|
424
|
+
|
|
425
|
+
task._run_task()
|
|
426
|
+
|
|
427
|
+
# Verify the update was called with the key names as defaults
|
|
428
|
+
task.sf.Account.update.assert_called_once_with(
|
|
429
|
+
"001xx000003DGbXXXX",
|
|
430
|
+
{"Name": "NONEXISTENT_VAR", "Status__c": "ANOTHER_MISSING_VAR"},
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
def test_values_and_transform_values_combined(self):
|
|
434
|
+
"""Test that values and transform_values can be combined, with transform_values overriding"""
|
|
435
|
+
import os
|
|
436
|
+
|
|
437
|
+
os.environ["TEST_OVERRIDE_NAME"] = "OverriddenName"
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
task = create_task(
|
|
441
|
+
UpdateRecord,
|
|
442
|
+
{
|
|
443
|
+
"object": "Account",
|
|
444
|
+
"record_id": "001xx000003DGbXXXX",
|
|
445
|
+
"values": "Name:OriginalName,Type:Customer",
|
|
446
|
+
"transform_values": "Name:TEST_OVERRIDE_NAME",
|
|
447
|
+
},
|
|
448
|
+
)
|
|
449
|
+
task._init_task()
|
|
450
|
+
task.sf = mock.Mock()
|
|
451
|
+
task.api = task.sf
|
|
452
|
+
task.sf.Account.update.return_value = 204
|
|
453
|
+
|
|
454
|
+
task._run_task()
|
|
455
|
+
|
|
456
|
+
# Verify transform_values overrides values for Name, but Type remains
|
|
457
|
+
task.sf.Account.update.assert_called_once_with(
|
|
458
|
+
"001xx000003DGbXXXX",
|
|
459
|
+
{"Name": "OverriddenName", "Type": "Customer"},
|
|
460
|
+
)
|
|
461
|
+
finally:
|
|
462
|
+
del os.environ["TEST_OVERRIDE_NAME"]
|
|
463
|
+
|
|
464
|
+
def test_transform_values_with_where_clause(self):
|
|
465
|
+
"""Test transform_values works with where clause for multiple records using bulk API"""
|
|
466
|
+
import os
|
|
467
|
+
|
|
468
|
+
os.environ["TEST_STATUS_VALUE"] = "Completed"
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
task = create_task(
|
|
472
|
+
UpdateRecord,
|
|
473
|
+
{
|
|
474
|
+
"object": "Account",
|
|
475
|
+
"where": "Type:Customer",
|
|
476
|
+
"transform_values": "Status__c:TEST_STATUS_VALUE",
|
|
477
|
+
},
|
|
478
|
+
)
|
|
479
|
+
task._init_task()
|
|
480
|
+
task.sf = mock.Mock()
|
|
481
|
+
task.api = task.sf
|
|
482
|
+
task.sf.query.return_value = {
|
|
483
|
+
"records": [
|
|
484
|
+
{"Id": "001xx000001Record1"},
|
|
485
|
+
{"Id": "001xx000001Record2"},
|
|
486
|
+
],
|
|
487
|
+
"totalSize": 2,
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
# Mock bulk API
|
|
491
|
+
task.bulk = mock.Mock()
|
|
492
|
+
bulk_result = mock.Mock()
|
|
493
|
+
bulk_result.success = True
|
|
494
|
+
task.bulk.update.return_value = [bulk_result, bulk_result]
|
|
495
|
+
|
|
496
|
+
task._run_task()
|
|
497
|
+
|
|
498
|
+
# Verify bulk update was called with environment variable values
|
|
499
|
+
task.bulk.update.assert_called_once()
|
|
500
|
+
call_args = task.bulk.update.call_args[0]
|
|
501
|
+
assert call_args[0] == "Account"
|
|
502
|
+
assert len(call_args[1]) == 2
|
|
503
|
+
assert call_args[1][0] == {
|
|
504
|
+
"Id": "001xx000001Record1",
|
|
505
|
+
"Status__c": "Completed",
|
|
506
|
+
}
|
|
507
|
+
assert call_args[1][1] == {
|
|
508
|
+
"Id": "001xx000001Record2",
|
|
509
|
+
"Status__c": "Completed",
|
|
510
|
+
}
|
|
511
|
+
finally:
|
|
512
|
+
del os.environ["TEST_STATUS_VALUE"]
|
|
@@ -56,7 +56,7 @@ class UpdateDependencies(BaseSalesforceTask):
|
|
|
56
56
|
"description": "The name of a sequence of resolution_strategy (from project__dependency_resolutions) to apply to dynamic dependencies."
|
|
57
57
|
},
|
|
58
58
|
"packages_only": {
|
|
59
|
-
"description": "Install only packaged dependencies. Ignore all
|
|
59
|
+
"description": "Install only packaged dependencies. Ignore all unpackaged metadata. Defaults to False."
|
|
60
60
|
},
|
|
61
61
|
"interactive": {
|
|
62
62
|
"description": "If True, stop after identifying all dependencies and output the package Ids that will be installed. Defaults to False."
|
|
@@ -65,7 +65,7 @@ class UpdateDependencies(BaseSalesforceTask):
|
|
|
65
65
|
"description": "If `interactive` is set to True, display package Ids using a format string ({} will be replaced with the package Id)."
|
|
66
66
|
},
|
|
67
67
|
"force_pre_post_install": {
|
|
68
|
-
"description": "Forces the
|
|
68
|
+
"description": "Forces the dependency_flow_pre flows and dependency_flow_post flows to run even if the dependency version is already installed. Defaults to False."
|
|
69
69
|
},
|
|
70
70
|
**{k: v for k, v in PACKAGE_INSTALL_TASK_OPTIONS.items() if k != "password"},
|
|
71
71
|
}
|