cumulusci-plus 5.0.17__py3-none-any.whl → 5.0.19__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/core/dependencies/base.py +77 -33
- cumulusci/core/dependencies/dependencies.py +27 -17
- cumulusci/core/dependencies/tests/test_dependencies.py +12 -6
- cumulusci/core/dependencies/tests/test_resolvers.py +61 -27
- cumulusci/cumulusci.yml +12 -0
- cumulusci/tasks/salesforce/SfDataCommands.py +400 -0
- cumulusci/tasks/salesforce/profiles.py +36 -7
- cumulusci/tasks/salesforce/tests/test_SfDataCommands.py +363 -0
- cumulusci/tasks/salesforce/tests/test_profiles.py +125 -1
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
- cumulusci/tasks/salesforce/update_dependencies.py +9 -3
- cumulusci/tasks/utility/env_management.py +2 -6
- {cumulusci_plus-5.0.17.dist-info → cumulusci_plus-5.0.19.dist-info}/METADATA +4 -4
- {cumulusci_plus-5.0.17.dist-info → cumulusci_plus-5.0.19.dist-info}/RECORD +19 -17
- {cumulusci_plus-5.0.17.dist-info → cumulusci_plus-5.0.19.dist-info}/WHEEL +0 -0
- {cumulusci_plus-5.0.17.dist-info → cumulusci_plus-5.0.19.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.17.dist-info → cumulusci_plus-5.0.19.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.17.dist-info → cumulusci_plus-5.0.19.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import sarge
|
|
6
|
+
|
|
7
|
+
from cumulusci.core.exceptions import SalesforceDXException
|
|
8
|
+
from cumulusci.tasks.salesforce.SfDataCommands import (
|
|
9
|
+
DataCreateRecordTask,
|
|
10
|
+
DataDeleteRecordTask,
|
|
11
|
+
DataQueryTask,
|
|
12
|
+
SfDataCommands,
|
|
13
|
+
SfDataToolingAPISupportedCommands,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from . import create_task
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def create_mock_sarge_command(stdout="", stderr="", returncode=0):
|
|
20
|
+
"""Create a mock sarge.Command that satisfies type checking"""
|
|
21
|
+
mock_command = mock.Mock(spec=sarge.Command)
|
|
22
|
+
mock_command.returncode = returncode
|
|
23
|
+
|
|
24
|
+
# Mock stdout_text
|
|
25
|
+
stdout_lines = stdout.split("\n") if stdout else []
|
|
26
|
+
mock_stdout_text = mock.Mock()
|
|
27
|
+
mock_stdout_text.__iter__ = mock.Mock(return_value=iter(stdout_lines))
|
|
28
|
+
mock_stdout_text.read = mock.Mock(return_value=stdout)
|
|
29
|
+
mock_command.stdout_text = mock_stdout_text
|
|
30
|
+
|
|
31
|
+
# Mock stderr_text
|
|
32
|
+
stderr_lines = stderr.split("\n") if stderr else []
|
|
33
|
+
mock_stderr_text = mock.Mock()
|
|
34
|
+
mock_stderr_text.__iter__ = mock.Mock(return_value=iter(stderr_lines))
|
|
35
|
+
mock_stderr_text.read = mock.Mock(return_value=stderr)
|
|
36
|
+
mock_command.stderr_text = mock_stderr_text
|
|
37
|
+
|
|
38
|
+
return mock_command
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mock.patch("cumulusci.tasks.salesforce.SfDataCommands.sfdx")
|
|
42
|
+
class TestSfDataCommands:
|
|
43
|
+
def test_init_options_basic(self, mock_sfdx):
|
|
44
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
45
|
+
task = create_task(SfDataCommands, {})
|
|
46
|
+
task()
|
|
47
|
+
assert task.data_command == "data "
|
|
48
|
+
assert task.args == []
|
|
49
|
+
|
|
50
|
+
def test_init_options_all_flags(self, mock_sfdx):
|
|
51
|
+
json_response = {"status": 0, "result": {}}
|
|
52
|
+
mock_sfdx.return_value = create_mock_sarge_command(
|
|
53
|
+
stdout=json.dumps(json_response)
|
|
54
|
+
)
|
|
55
|
+
task = create_task(
|
|
56
|
+
SfDataCommands,
|
|
57
|
+
{
|
|
58
|
+
"json_output": True,
|
|
59
|
+
"api_version": "50.0",
|
|
60
|
+
"flags_dir": "/tmp/flags",
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
task()
|
|
64
|
+
expected_args = [
|
|
65
|
+
"--flags-dir ",
|
|
66
|
+
"/tmp/flags",
|
|
67
|
+
"--json",
|
|
68
|
+
"--api_version",
|
|
69
|
+
"50.0",
|
|
70
|
+
]
|
|
71
|
+
assert all(arg in task.args for arg in expected_args)
|
|
72
|
+
|
|
73
|
+
def test_load_json_output_success(self, mock_sfdx):
|
|
74
|
+
json_response = {"status": 0, "result": {"records": []}}
|
|
75
|
+
stdout = json.dumps(json_response)
|
|
76
|
+
mock_command = create_mock_sarge_command(stdout=stdout)
|
|
77
|
+
mock_sfdx.return_value = mock_command
|
|
78
|
+
|
|
79
|
+
task = create_task(SfDataCommands, {"json_output": True})
|
|
80
|
+
task()
|
|
81
|
+
|
|
82
|
+
assert task.return_values == json_response
|
|
83
|
+
|
|
84
|
+
def test_load_json_output_decode_error(self, mock_sfdx):
|
|
85
|
+
mock_command = create_mock_sarge_command(stdout="invalid json")
|
|
86
|
+
mock_sfdx.return_value = mock_command
|
|
87
|
+
|
|
88
|
+
task = create_task(SfDataCommands, {"json_output": True})
|
|
89
|
+
|
|
90
|
+
with pytest.raises(SalesforceDXException, match="Failed to parse the output"):
|
|
91
|
+
task()
|
|
92
|
+
|
|
93
|
+
def test_logging_stdout_and_stderr(self, mock_sfdx):
|
|
94
|
+
mock_command = create_mock_sarge_command(
|
|
95
|
+
stdout="Success: Query completed\nFound 5 records",
|
|
96
|
+
stderr="Warning: Some non-critical issue",
|
|
97
|
+
)
|
|
98
|
+
mock_sfdx.return_value = mock_command
|
|
99
|
+
|
|
100
|
+
task = create_task(SfDataCommands, {})
|
|
101
|
+
with mock.patch.object(task, "logger") as mock_logger:
|
|
102
|
+
task()
|
|
103
|
+
|
|
104
|
+
# Check that stdout lines were logged as info
|
|
105
|
+
mock_logger.info.assert_any_call("Success: Query completed")
|
|
106
|
+
mock_logger.info.assert_any_call("Found 5 records")
|
|
107
|
+
|
|
108
|
+
# Check that stderr lines were logged as error
|
|
109
|
+
mock_logger.error.assert_called_with("Warning: Some non-critical issue")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@mock.patch("cumulusci.tasks.salesforce.SfDataCommands.sfdx")
|
|
113
|
+
class TestSfDataToolingAPISupportedCommands:
|
|
114
|
+
def test_inherits_from_base(self, mock_sfdx):
|
|
115
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
116
|
+
task = create_task(SfDataToolingAPISupportedCommands, {})
|
|
117
|
+
assert isinstance(task, SfDataCommands)
|
|
118
|
+
|
|
119
|
+
def test_use_tooling_api_option(self, mock_sfdx):
|
|
120
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
121
|
+
task = create_task(SfDataToolingAPISupportedCommands, {"use_tooling_api": True})
|
|
122
|
+
# Test that the option is available (would be tested in subclasses that use it)
|
|
123
|
+
assert hasattr(task.parsed_options, "use_tooling_api")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@mock.patch("cumulusci.tasks.salesforce.SfDataCommands.sfdx")
|
|
127
|
+
class TestDataQueryTask:
|
|
128
|
+
def test_init_task_sets_command(self, mock_sfdx):
|
|
129
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
130
|
+
task = create_task(DataQueryTask, {"query": "SELECT Id FROM Account"})
|
|
131
|
+
task()
|
|
132
|
+
assert task.data_command == "data query"
|
|
133
|
+
|
|
134
|
+
def test_init_options_all_parameters(self, mock_sfdx):
|
|
135
|
+
json_response = {"status": 0, "result": {"records": []}}
|
|
136
|
+
mock_sfdx.return_value = create_mock_sarge_command(
|
|
137
|
+
stdout=json.dumps(json_response)
|
|
138
|
+
)
|
|
139
|
+
task = create_task(
|
|
140
|
+
DataQueryTask,
|
|
141
|
+
{
|
|
142
|
+
"query": "SELECT Id FROM Account",
|
|
143
|
+
"file": "/tmp/query.soql",
|
|
144
|
+
"all_rows": True,
|
|
145
|
+
"result_format": "csv",
|
|
146
|
+
"output_file": "/tmp/output.csv",
|
|
147
|
+
"api_version": "50.0",
|
|
148
|
+
"flags_dir": "/tmp/flags",
|
|
149
|
+
"json_output": True,
|
|
150
|
+
"use_tooling_api": True,
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
task()
|
|
154
|
+
|
|
155
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
156
|
+
assert mock_sfdx.call_args[1]["log_note"] == "Running data command"
|
|
157
|
+
assert "--query" in call_args
|
|
158
|
+
assert "SELECT Id FROM Account" in call_args
|
|
159
|
+
assert "--file" in call_args
|
|
160
|
+
assert "/tmp/query.soql" in call_args
|
|
161
|
+
assert "--all-rows" in call_args
|
|
162
|
+
assert "--result-format" in call_args
|
|
163
|
+
assert "csv" in call_args
|
|
164
|
+
assert "--output-file" in call_args
|
|
165
|
+
assert "/tmp/output.csv" in call_args
|
|
166
|
+
assert "--api_version" in call_args
|
|
167
|
+
assert "50.0" in call_args
|
|
168
|
+
assert "--flags-dir " in call_args
|
|
169
|
+
assert "/tmp/flags" in call_args
|
|
170
|
+
assert "--json" in call_args
|
|
171
|
+
|
|
172
|
+
def test_minimal_options(self, mock_sfdx):
|
|
173
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
174
|
+
task = create_task(DataQueryTask, {"query": "SELECT Id FROM Account"})
|
|
175
|
+
task()
|
|
176
|
+
|
|
177
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
178
|
+
assert "--query" in call_args
|
|
179
|
+
assert "SELECT Id FROM Account" in call_args
|
|
180
|
+
|
|
181
|
+
def test_json_output_logging(self, mock_sfdx):
|
|
182
|
+
json_response = {"status": 0, "result": {"records": [{"Id": "123"}]}}
|
|
183
|
+
stdout = json.dumps(json_response)
|
|
184
|
+
mock_sfdx.return_value = create_mock_sarge_command(stdout=stdout)
|
|
185
|
+
|
|
186
|
+
task = create_task(
|
|
187
|
+
DataQueryTask,
|
|
188
|
+
{"query": "SELECT Id FROM Account", "json_output": True},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
with mock.patch.object(task, "logger") as mock_logger:
|
|
192
|
+
task()
|
|
193
|
+
mock_logger.info.assert_called_with(json_response)
|
|
194
|
+
|
|
195
|
+
def test_run_task_json_decode_error(self, mock_sfdx):
|
|
196
|
+
mock_sfdx.return_value = create_mock_sarge_command(stdout="this is not json")
|
|
197
|
+
task = create_task(
|
|
198
|
+
DataQueryTask,
|
|
199
|
+
{"query": "SELECT Id FROM Account", "json_output": True},
|
|
200
|
+
)
|
|
201
|
+
with pytest.raises(
|
|
202
|
+
SalesforceDXException,
|
|
203
|
+
match="Failed to parse the output of the data query command",
|
|
204
|
+
):
|
|
205
|
+
task()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@mock.patch("cumulusci.tasks.salesforce.SfDataCommands.sfdx")
|
|
209
|
+
class TestDataCreateRecordTask:
|
|
210
|
+
def test_init_task_sets_command(self, mock_sfdx):
|
|
211
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
212
|
+
task = create_task(
|
|
213
|
+
DataCreateRecordTask,
|
|
214
|
+
{"sobject": "Account", "values": "Name='Test Account'"},
|
|
215
|
+
)
|
|
216
|
+
task()
|
|
217
|
+
assert task.data_command == "data create record"
|
|
218
|
+
|
|
219
|
+
def test_init_options_all_parameters(self, mock_sfdx):
|
|
220
|
+
json_response = {"status": 0, "result": {"id": "001000000000001"}}
|
|
221
|
+
mock_sfdx.return_value = create_mock_sarge_command(
|
|
222
|
+
stdout=json.dumps(json_response)
|
|
223
|
+
)
|
|
224
|
+
task = create_task(
|
|
225
|
+
DataCreateRecordTask,
|
|
226
|
+
{
|
|
227
|
+
"sobject": "Account",
|
|
228
|
+
"values": "Name='Test Account' Industry='Technology'",
|
|
229
|
+
"api_version": "50.0",
|
|
230
|
+
"flags_dir": "/tmp/flags",
|
|
231
|
+
"json_output": True,
|
|
232
|
+
"use_tooling_api": True,
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
task()
|
|
236
|
+
|
|
237
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
238
|
+
assert mock_sfdx.call_args[1]["log_note"] == "Running data command"
|
|
239
|
+
assert "--sobject" in call_args
|
|
240
|
+
assert "Account" in call_args
|
|
241
|
+
assert "--values" in call_args
|
|
242
|
+
assert "Name='Test Account' Industry='Technology'" in call_args
|
|
243
|
+
assert "--api_version" in call_args
|
|
244
|
+
assert "50.0" in call_args
|
|
245
|
+
assert "--flags-dir " in call_args
|
|
246
|
+
assert "/tmp/flags" in call_args
|
|
247
|
+
assert "--json" in call_args
|
|
248
|
+
|
|
249
|
+
def test_minimal_options(self, mock_sfdx):
|
|
250
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
251
|
+
task = create_task(
|
|
252
|
+
DataCreateRecordTask,
|
|
253
|
+
{"sobject": "Contact", "values": "LastName='Doe'"},
|
|
254
|
+
)
|
|
255
|
+
task()
|
|
256
|
+
|
|
257
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
258
|
+
assert "--sobject" in call_args
|
|
259
|
+
assert "Contact" in call_args
|
|
260
|
+
assert "--values" in call_args
|
|
261
|
+
assert "LastName='Doe'" in call_args
|
|
262
|
+
|
|
263
|
+
def test_missing_required_options(self, mock_sfdx):
|
|
264
|
+
# Test that required field validation works as expected
|
|
265
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
266
|
+
from cumulusci.core.exceptions import TaskOptionsError
|
|
267
|
+
|
|
268
|
+
with pytest.raises(TaskOptionsError, match="field required"):
|
|
269
|
+
create_task(DataCreateRecordTask, {})
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@mock.patch("cumulusci.tasks.salesforce.SfDataCommands.sfdx")
|
|
273
|
+
class TestDataDeleteRecordTask:
|
|
274
|
+
def test_init_task_sets_command(self, mock_sfdx):
|
|
275
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
276
|
+
task = create_task(
|
|
277
|
+
DataDeleteRecordTask, {"sobject": "Account", "record_id": "001000000000001"}
|
|
278
|
+
)
|
|
279
|
+
task()
|
|
280
|
+
assert task.data_command == "data delete record"
|
|
281
|
+
|
|
282
|
+
def test_delete_by_record_id(self, mock_sfdx):
|
|
283
|
+
json_response = {"status": 0, "result": {}}
|
|
284
|
+
mock_sfdx.return_value = create_mock_sarge_command(
|
|
285
|
+
stdout=json.dumps(json_response)
|
|
286
|
+
)
|
|
287
|
+
task = create_task(
|
|
288
|
+
DataDeleteRecordTask,
|
|
289
|
+
{
|
|
290
|
+
"sobject": "Account",
|
|
291
|
+
"record_id": "001000000000001AAA",
|
|
292
|
+
"api_version": "50.0",
|
|
293
|
+
"flags_dir": "/tmp/flags",
|
|
294
|
+
"json_output": True,
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
task()
|
|
298
|
+
|
|
299
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
300
|
+
assert mock_sfdx.call_args[1]["log_note"] == "Running data command"
|
|
301
|
+
assert "--sobject" in call_args
|
|
302
|
+
assert "Account" in call_args
|
|
303
|
+
assert "--record-id" in call_args
|
|
304
|
+
assert "001000000000001AAA" in call_args
|
|
305
|
+
assert "--api_version" in call_args
|
|
306
|
+
assert "50.0" in call_args
|
|
307
|
+
assert "--flags-dir " in call_args
|
|
308
|
+
assert "/tmp/flags" in call_args
|
|
309
|
+
assert "--json" in call_args
|
|
310
|
+
|
|
311
|
+
def test_delete_by_where_clause(self, mock_sfdx):
|
|
312
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
313
|
+
task = create_task(
|
|
314
|
+
DataDeleteRecordTask,
|
|
315
|
+
{
|
|
316
|
+
"sobject": "Account",
|
|
317
|
+
"where": "Name='Test Account' AND Industry='Technology'",
|
|
318
|
+
"use_tooling_api": True,
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
task()
|
|
322
|
+
|
|
323
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
324
|
+
# Check that sfdx was called with the right log_note
|
|
325
|
+
assert mock_sfdx.call_args[1]["log_note"] == "Running data command"
|
|
326
|
+
assert "--sobject" in call_args
|
|
327
|
+
assert "Account" in call_args
|
|
328
|
+
assert "--where" in call_args
|
|
329
|
+
assert "Name='Test Account' AND Industry='Technology'" in call_args
|
|
330
|
+
|
|
331
|
+
def test_minimal_options(self, mock_sfdx):
|
|
332
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
333
|
+
task = create_task(
|
|
334
|
+
DataDeleteRecordTask,
|
|
335
|
+
{"sobject": "Contact"},
|
|
336
|
+
)
|
|
337
|
+
task()
|
|
338
|
+
|
|
339
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
340
|
+
assert "--sobject" in call_args
|
|
341
|
+
assert "Contact" in call_args
|
|
342
|
+
# Should not have --record-id or --where if not provided
|
|
343
|
+
assert "--record-id" not in call_args
|
|
344
|
+
assert "--where" not in call_args
|
|
345
|
+
|
|
346
|
+
def test_both_record_id_and_where_provided(self, mock_sfdx):
|
|
347
|
+
# Test that both options can be provided (though this might not be practical)
|
|
348
|
+
mock_sfdx.return_value = create_mock_sarge_command()
|
|
349
|
+
task = create_task(
|
|
350
|
+
DataDeleteRecordTask,
|
|
351
|
+
{
|
|
352
|
+
"sobject": "Account",
|
|
353
|
+
"record_id": "001000000000001",
|
|
354
|
+
"where": "Name='Test Account'",
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
task()
|
|
358
|
+
|
|
359
|
+
call_args = mock_sfdx.call_args[1]["args"]
|
|
360
|
+
assert "--record-id" in call_args
|
|
361
|
+
assert "001000000000001" in call_args
|
|
362
|
+
assert "--where" in call_args
|
|
363
|
+
assert "Name='Test Account'" in call_args
|
|
@@ -95,6 +95,15 @@ def test_run_task_success():
|
|
|
95
95
|
json={
|
|
96
96
|
"done": True,
|
|
97
97
|
"totalSize": 1,
|
|
98
|
+
"records": [],
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
responses.add(
|
|
102
|
+
responses.GET,
|
|
103
|
+
query_url,
|
|
104
|
+
json={
|
|
105
|
+
"done": True,
|
|
106
|
+
"totalSize": 0,
|
|
98
107
|
"records": [
|
|
99
108
|
{
|
|
100
109
|
"attributes": {
|
|
@@ -115,9 +124,12 @@ def test_run_task_success():
|
|
|
115
124
|
result = task._run_task()
|
|
116
125
|
assert result == "001R0000029IyDPIA0"
|
|
117
126
|
assert responses.calls[0].request.params == {
|
|
127
|
+
"q": "SELECT Id, Name FROM Profile WHERE FullName = 'Test Profile Name' LIMIT 1"
|
|
128
|
+
}
|
|
129
|
+
assert responses.calls[1].request.params == {
|
|
118
130
|
"q": "SELECT Id, Name FROM UserLicense WHERE Name = 'Foo' LIMIT 1"
|
|
119
131
|
}
|
|
120
|
-
soap_body = responses.calls[
|
|
132
|
+
soap_body = responses.calls[2].request.body
|
|
121
133
|
assert "<Name>Test Profile Name</Name>" in str(soap_body)
|
|
122
134
|
assert "<UserLicenseId>10056000000VGjUAAW</UserLicenseId>" in str(soap_body)
|
|
123
135
|
assert "<Description>Have fun stormin da castle</Description>" in str(soap_body)
|
|
@@ -135,6 +147,15 @@ def test_run_task_fault():
|
|
|
135
147
|
)
|
|
136
148
|
task.org_config._latest_api_version = "53.0"
|
|
137
149
|
|
|
150
|
+
responses.add(
|
|
151
|
+
responses.GET,
|
|
152
|
+
"https://test.salesforce.com/services/data/v53.0/query/",
|
|
153
|
+
json={
|
|
154
|
+
"done": True,
|
|
155
|
+
"totalSize": 0,
|
|
156
|
+
"records": [],
|
|
157
|
+
},
|
|
158
|
+
)
|
|
138
159
|
responses.add(
|
|
139
160
|
responses.POST,
|
|
140
161
|
"https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
|
|
@@ -157,6 +178,17 @@ def test_run_task_field_error():
|
|
|
157
178
|
},
|
|
158
179
|
)
|
|
159
180
|
task.org_config._latest_api_version = "53.0"
|
|
181
|
+
|
|
182
|
+
responses.add(
|
|
183
|
+
responses.GET,
|
|
184
|
+
"https://test.salesforce.com/services/data/v53.0/query/",
|
|
185
|
+
json={
|
|
186
|
+
"done": True,
|
|
187
|
+
"totalSize": 0,
|
|
188
|
+
"records": [],
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
|
|
160
192
|
responses.add(
|
|
161
193
|
responses.POST,
|
|
162
194
|
"https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
|
|
@@ -180,6 +212,16 @@ def test_run_task_error():
|
|
|
180
212
|
)
|
|
181
213
|
task.org_config._latest_api_version = "53.0"
|
|
182
214
|
|
|
215
|
+
responses.add(
|
|
216
|
+
responses.GET,
|
|
217
|
+
"https://test.salesforce.com/services/data/v53.0/query/",
|
|
218
|
+
json={
|
|
219
|
+
"done": True,
|
|
220
|
+
"totalSize": 0,
|
|
221
|
+
"records": [],
|
|
222
|
+
},
|
|
223
|
+
)
|
|
224
|
+
|
|
183
225
|
responses.add(
|
|
184
226
|
responses.POST,
|
|
185
227
|
"https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
|
|
@@ -200,3 +242,85 @@ def test_task_options_error():
|
|
|
200
242
|
"description": "Foo",
|
|
201
243
|
},
|
|
202
244
|
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@responses.activate
|
|
248
|
+
def test_run_task_success_with_collision_check():
|
|
249
|
+
query_url = "https://test.salesforce.com/services/data/v53.0/query/"
|
|
250
|
+
|
|
251
|
+
task = create_task(
|
|
252
|
+
CreateBlankProfile,
|
|
253
|
+
{
|
|
254
|
+
"license": "Foo",
|
|
255
|
+
"name": "Test Profile Name",
|
|
256
|
+
"description": "Have fun stormin da castle",
|
|
257
|
+
},
|
|
258
|
+
)
|
|
259
|
+
task.org_config._latest_api_version = "53.0"
|
|
260
|
+
|
|
261
|
+
responses.add(
|
|
262
|
+
responses.GET,
|
|
263
|
+
query_url,
|
|
264
|
+
json={
|
|
265
|
+
"done": True,
|
|
266
|
+
"totalSize": 1,
|
|
267
|
+
"records": [
|
|
268
|
+
{
|
|
269
|
+
"attributes": {
|
|
270
|
+
"type": "Profile",
|
|
271
|
+
"url": "/services/data/v53.0/sobjects/Profile/10056000000VGjUAAW",
|
|
272
|
+
},
|
|
273
|
+
"Id": "10056000000VGjUAAW",
|
|
274
|
+
"Name": "Test Profile Name",
|
|
275
|
+
}
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
result = task._run_task()
|
|
281
|
+
assert result == "10056000000VGjUAAW"
|
|
282
|
+
assert responses.calls[0].request.params == {
|
|
283
|
+
"q": "SELECT Id, Name FROM Profile WHERE FullName = 'Test Profile Name' LIMIT 1"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@responses.activate
|
|
288
|
+
def test_run_task_with_invalid_license():
|
|
289
|
+
query_url = "https://test.salesforce.com/services/data/v53.0/query/"
|
|
290
|
+
|
|
291
|
+
task = create_task(
|
|
292
|
+
CreateBlankProfile,
|
|
293
|
+
{
|
|
294
|
+
"license": "Foo",
|
|
295
|
+
"name": "Test Profile Name",
|
|
296
|
+
"description": "Have fun stormin da castle",
|
|
297
|
+
},
|
|
298
|
+
)
|
|
299
|
+
task.org_config._latest_api_version = "53.0"
|
|
300
|
+
|
|
301
|
+
responses.add(
|
|
302
|
+
responses.GET,
|
|
303
|
+
query_url,
|
|
304
|
+
json={
|
|
305
|
+
"done": True,
|
|
306
|
+
"totalSize": 1,
|
|
307
|
+
"records": [],
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
responses.add(
|
|
311
|
+
responses.GET,
|
|
312
|
+
query_url,
|
|
313
|
+
json={
|
|
314
|
+
"done": True,
|
|
315
|
+
"totalSize": 0,
|
|
316
|
+
"records": [],
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
responses.add(
|
|
320
|
+
responses.POST,
|
|
321
|
+
"https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
|
|
322
|
+
RESPONSE_SUCCESS,
|
|
323
|
+
)
|
|
324
|
+
with pytest.raises(TaskOptionsError) as e:
|
|
325
|
+
task._run_task()
|
|
326
|
+
assert "License name 'Foo' was not found." in str(e)
|
|
@@ -362,7 +362,7 @@ def test_install_dependency_installs_unmanaged():
|
|
|
362
362
|
|
|
363
363
|
task._install_dependency(task.dependencies[0])
|
|
364
364
|
task.dependencies[0].install.assert_called_once_with(
|
|
365
|
-
task.project_config, task.org_config
|
|
365
|
+
task.project_config, task.org_config, task.options
|
|
366
366
|
)
|
|
367
367
|
|
|
368
368
|
|
|
@@ -64,6 +64,9 @@ class UpdateDependencies(BaseSalesforceTask):
|
|
|
64
64
|
"base_package_url_format": {
|
|
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
|
+
"force_pre_post_install": {
|
|
68
|
+
"description": "Forces the pre-install and post-install steps to be run. Defaults to False."
|
|
69
|
+
},
|
|
67
70
|
**{k: v for k, v in PACKAGE_INSTALL_TASK_OPTIONS.items() if k != "password"},
|
|
68
71
|
}
|
|
69
72
|
|
|
@@ -220,8 +223,11 @@ class UpdateDependencies(BaseSalesforceTask):
|
|
|
220
223
|
if not click.confirm("Continue to install dependencies?", default=True):
|
|
221
224
|
raise CumulusCIException("Dependency installation was canceled.")
|
|
222
225
|
|
|
223
|
-
|
|
224
|
-
self.
|
|
226
|
+
if dependencies:
|
|
227
|
+
self.logger.info("Installing dependencies:")
|
|
228
|
+
|
|
229
|
+
for d in dependencies:
|
|
230
|
+
self._install_dependency(d)
|
|
225
231
|
|
|
226
232
|
self.org_config.reset_installed_packages()
|
|
227
233
|
|
|
@@ -233,7 +239,7 @@ class UpdateDependencies(BaseSalesforceTask):
|
|
|
233
239
|
self.project_config, self.org_config, self.install_options
|
|
234
240
|
)
|
|
235
241
|
else:
|
|
236
|
-
dependency.install(self.project_config, self.org_config)
|
|
242
|
+
dependency.install(self.project_config, self.org_config, self.options)
|
|
237
243
|
|
|
238
244
|
def freeze(self, step):
|
|
239
245
|
if self.options["interactive"]:
|
|
@@ -109,14 +109,10 @@ class EnvManagementOption(CCIOptions):
|
|
|
109
109
|
f"Formatting Error: {value} for datatype: {datatype} - {e}"
|
|
110
110
|
)
|
|
111
111
|
|
|
112
|
-
if self.set
|
|
112
|
+
if self.set:
|
|
113
113
|
os.environ[self.name] = str(task_values[self.name])
|
|
114
114
|
|
|
115
|
-
if
|
|
116
|
-
self.set
|
|
117
|
-
and self.datatype == "vcs_repo"
|
|
118
|
-
and f"{self.name}_BRANCH" not in os.environ
|
|
119
|
-
):
|
|
115
|
+
if self.set and self.datatype == "vcs_repo":
|
|
120
116
|
os.environ[f"{self.name}_BRANCH"] = str(task_values[f"{self.name}_BRANCH"])
|
|
121
117
|
|
|
122
118
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cumulusci-plus
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.19
|
|
4
4
|
Summary: Build and release tools for Salesforce developers
|
|
5
5
|
Project-URL: Homepage, https://github.com/jorgesolebur/CumulusCI
|
|
6
6
|
Project-URL: Changelog, https://cumulusci.readthedocs.io/en/stable/history.html
|
|
@@ -127,7 +127,7 @@ license](https://github.com/SFDO-Tooling/CumulusCI/blob/main/LICENSE)
|
|
|
127
127
|
and is not covered by the Salesforce Master Subscription Agreement.
|
|
128
128
|
|
|
129
129
|
<!-- Changelog -->
|
|
130
|
-
## v5.0.
|
|
130
|
+
## v5.0.19 (2025-09-04)
|
|
131
131
|
|
|
132
132
|
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
133
133
|
|
|
@@ -135,6 +135,6 @@ and is not covered by the Salesforce Master Subscription Agreement.
|
|
|
135
135
|
|
|
136
136
|
### Changes
|
|
137
137
|
|
|
138
|
-
-
|
|
138
|
+
- Feature/pm 2055 pre post flow by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#63](https://github.com/jorgesolebur/CumulusCI/pull/63)
|
|
139
139
|
|
|
140
|
-
**Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.
|
|
140
|
+
**Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.18...v5.0.19
|