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.
Files changed (135) hide show
  1. cumulusci/__about__.py +1 -1
  2. cumulusci/cli/logger.py +2 -2
  3. cumulusci/cli/service.py +20 -0
  4. cumulusci/cli/task.py +19 -3
  5. cumulusci/cli/tests/test_error.py +3 -1
  6. cumulusci/cli/tests/test_flow.py +279 -2
  7. cumulusci/cli/tests/test_org.py +5 -0
  8. cumulusci/cli/tests/test_service.py +15 -12
  9. cumulusci/cli/tests/test_task.py +122 -2
  10. cumulusci/cli/tests/utils.py +1 -4
  11. cumulusci/core/config/__init__.py +1 -0
  12. cumulusci/core/config/base_task_flow_config.py +26 -1
  13. cumulusci/core/config/org_config.py +2 -1
  14. cumulusci/core/config/project_config.py +14 -20
  15. cumulusci/core/config/scratch_org_config.py +12 -0
  16. cumulusci/core/config/tests/test_config.py +1 -0
  17. cumulusci/core/config/tests/test_config_expensive.py +9 -3
  18. cumulusci/core/config/universal_config.py +3 -4
  19. cumulusci/core/dependencies/base.py +5 -1
  20. cumulusci/core/dependencies/dependencies.py +1 -1
  21. cumulusci/core/dependencies/github.py +1 -2
  22. cumulusci/core/dependencies/resolvers.py +1 -1
  23. cumulusci/core/dependencies/tests/test_dependencies.py +1 -1
  24. cumulusci/core/dependencies/tests/test_resolvers.py +1 -1
  25. cumulusci/core/flowrunner.py +90 -6
  26. cumulusci/core/github.py +1 -1
  27. cumulusci/core/sfdx.py +3 -1
  28. cumulusci/core/source_transforms/tests/test_transforms.py +1 -1
  29. cumulusci/core/source_transforms/transforms.py +1 -1
  30. cumulusci/core/tasks.py +13 -2
  31. cumulusci/core/tests/test_flowrunner.py +100 -0
  32. cumulusci/core/tests/test_tasks.py +65 -0
  33. cumulusci/core/utils.py +3 -1
  34. cumulusci/core/versions.py +1 -1
  35. cumulusci/cumulusci.yml +73 -1
  36. cumulusci/oauth/client.py +1 -1
  37. cumulusci/plugins/plugin_base.py +5 -3
  38. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +1 -1
  39. cumulusci/salesforce_api/rest_deploy.py +1 -1
  40. cumulusci/schema/cumulusci.jsonschema.json +69 -0
  41. cumulusci/tasks/apex/anon.py +1 -1
  42. cumulusci/tasks/apex/testrunner.py +421 -144
  43. cumulusci/tasks/apex/tests/test_apex_tasks.py +917 -1
  44. cumulusci/tasks/bulkdata/extract.py +0 -1
  45. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +1 -1
  46. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +1 -1
  47. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +1 -1
  48. cumulusci/tasks/bulkdata/generate_and_load_data.py +136 -12
  49. cumulusci/tasks/bulkdata/mapping_parser.py +139 -44
  50. cumulusci/tasks/bulkdata/select_utils.py +1 -1
  51. cumulusci/tasks/bulkdata/snowfakery.py +100 -25
  52. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +159 -0
  53. cumulusci/tasks/bulkdata/tests/test_load.py +0 -2
  54. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +763 -1
  55. cumulusci/tasks/bulkdata/tests/test_select_utils.py +46 -0
  56. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +133 -0
  57. cumulusci/tasks/create_package_version.py +190 -16
  58. cumulusci/tasks/datadictionary.py +1 -1
  59. cumulusci/tasks/metadata_etl/__init__.py +2 -0
  60. cumulusci/tasks/metadata_etl/applications.py +256 -0
  61. cumulusci/tasks/metadata_etl/base.py +7 -3
  62. cumulusci/tasks/metadata_etl/layouts.py +1 -1
  63. cumulusci/tasks/metadata_etl/permissions.py +1 -1
  64. cumulusci/tasks/metadata_etl/remote_site_settings.py +2 -2
  65. cumulusci/tasks/metadata_etl/tests/test_applications.py +710 -0
  66. cumulusci/tasks/push/README.md +15 -17
  67. cumulusci/tasks/release_notes/README.md +13 -13
  68. cumulusci/tasks/release_notes/generator.py +13 -8
  69. cumulusci/tasks/robotframework/tests/test_robotframework.py +6 -1
  70. cumulusci/tasks/salesforce/Deploy.py +53 -2
  71. cumulusci/tasks/salesforce/SfPackageCommands.py +363 -0
  72. cumulusci/tasks/salesforce/__init__.py +1 -0
  73. cumulusci/tasks/salesforce/assign_ps_psg.py +448 -0
  74. cumulusci/tasks/salesforce/composite.py +1 -1
  75. cumulusci/tasks/salesforce/custom_settings_wait.py +1 -1
  76. cumulusci/tasks/salesforce/enable_prediction.py +5 -1
  77. cumulusci/tasks/salesforce/getPackageVersion.py +89 -0
  78. cumulusci/tasks/salesforce/insert_record.py +18 -19
  79. cumulusci/tasks/salesforce/sourcetracking.py +1 -1
  80. cumulusci/tasks/salesforce/tests/test_Deploy.py +316 -1
  81. cumulusci/tasks/salesforce/tests/test_SfPackageCommands.py +554 -0
  82. cumulusci/tasks/salesforce/tests/test_assign_ps_psg.py +1055 -0
  83. cumulusci/tasks/salesforce/tests/test_enable_prediction.py +4 -2
  84. cumulusci/tasks/salesforce/tests/test_getPackageVersion.py +651 -0
  85. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
  86. cumulusci/tasks/salesforce/tests/test_update_external_auth_identity_provider.py +927 -0
  87. cumulusci/tasks/salesforce/tests/test_update_external_credential.py +1427 -0
  88. cumulusci/tasks/salesforce/tests/test_update_named_credential.py +1042 -0
  89. cumulusci/tasks/salesforce/tests/test_update_record.py +512 -0
  90. cumulusci/tasks/salesforce/update_dependencies.py +2 -2
  91. cumulusci/tasks/salesforce/update_external_auth_identity_provider.py +551 -0
  92. cumulusci/tasks/salesforce/update_external_credential.py +647 -0
  93. cumulusci/tasks/salesforce/update_named_credential.py +441 -0
  94. cumulusci/tasks/salesforce/update_profile.py +17 -13
  95. cumulusci/tasks/salesforce/update_record.py +217 -0
  96. cumulusci/tasks/salesforce/users/permsets.py +62 -5
  97. cumulusci/tasks/salesforce/users/tests/test_permsets.py +237 -11
  98. cumulusci/tasks/sfdmu/__init__.py +0 -0
  99. cumulusci/tasks/sfdmu/sfdmu.py +376 -0
  100. cumulusci/tasks/sfdmu/tests/__init__.py +1 -0
  101. cumulusci/tasks/sfdmu/tests/test_runner.py +212 -0
  102. cumulusci/tasks/sfdmu/tests/test_sfdmu.py +1012 -0
  103. cumulusci/tasks/tests/test_create_package_version.py +716 -1
  104. cumulusci/tasks/tests/test_util.py +42 -0
  105. cumulusci/tasks/util.py +37 -1
  106. cumulusci/tasks/utility/copyContents.py +402 -0
  107. cumulusci/tasks/utility/credentialManager.py +302 -0
  108. cumulusci/tasks/utility/directoryRecreator.py +30 -0
  109. cumulusci/tasks/utility/env_management.py +1 -1
  110. cumulusci/tasks/utility/secretsToEnv.py +135 -0
  111. cumulusci/tasks/utility/tests/test_copyContents.py +1719 -0
  112. cumulusci/tasks/utility/tests/test_credentialManager.py +1150 -0
  113. cumulusci/tasks/utility/tests/test_directoryRecreator.py +439 -0
  114. cumulusci/tasks/utility/tests/test_secretsToEnv.py +1118 -0
  115. cumulusci/tests/test_integration_infrastructure.py +3 -1
  116. cumulusci/tests/test_utils.py +70 -6
  117. cumulusci/utils/__init__.py +54 -9
  118. cumulusci/utils/classutils.py +5 -2
  119. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +31 -30
  120. cumulusci/utils/options.py +23 -1
  121. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +1 -1
  122. cumulusci/utils/yaml/cumulusci_yml.py +8 -3
  123. cumulusci/utils/yaml/model_parser.py +2 -2
  124. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +1 -1
  125. cumulusci/utils/yaml/tests/test_model_parser.py +3 -3
  126. cumulusci/vcs/base.py +23 -15
  127. cumulusci/vcs/bootstrap.py +5 -4
  128. cumulusci/vcs/utils/list_modified_files.py +189 -0
  129. cumulusci/vcs/utils/tests/test_list_modified_files.py +588 -0
  130. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/METADATA +11 -10
  131. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/RECORD +135 -104
  132. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/WHEEL +1 -1
  133. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/entry_points.txt +0 -0
  134. {cumulusci_plus-5.0.21.dist-info → cumulusci_plus-5.0.43.dist-info}/licenses/AUTHORS.rst +0 -0
  135. {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 unmanaged metadata. Defaults to False."
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 pre-install and post-install steps to be run. Defaults to False."
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
  }