cumulusci-plus 5.0.18__py3-none-any.whl → 5.0.20__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.
Potentially problematic release.
This version of cumulusci-plus might be problematic. Click here for more details.
- 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 +0 -4
- cumulusci/tasks/salesforce/SfDataCommands.py +234 -0
- cumulusci/tasks/salesforce/profiles.py +40 -7
- cumulusci/tasks/salesforce/tests/test_profiles.py +165 -1
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +1 -1
- cumulusci/tasks/salesforce/update_dependencies.py +9 -3
- {cumulusci_plus-5.0.18.dist-info → cumulusci_plus-5.0.20.dist-info}/METADATA +4 -4
- {cumulusci_plus-5.0.18.dist-info → cumulusci_plus-5.0.20.dist-info}/RECORD +17 -19
- cumulusci/tasks/utility/data_management.py +0 -16
- cumulusci/tasks/utility/tests/test_data_management.py +0 -43
- {cumulusci_plus-5.0.18.dist-info → cumulusci_plus-5.0.20.dist-info}/WHEEL +0 -0
- {cumulusci_plus-5.0.18.dist-info → cumulusci_plus-5.0.20.dist-info}/entry_points.txt +0 -0
- {cumulusci_plus-5.0.18.dist-info → cumulusci_plus-5.0.20.dist-info}/licenses/AUTHORS.rst +0 -0
- {cumulusci_plus-5.0.18.dist-info → cumulusci_plus-5.0.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -164,3 +164,237 @@ class DataDeleteRecordTask(SfDataToolingAPISupportedCommands):
|
|
|
164
164
|
|
|
165
165
|
def _run_task(self):
|
|
166
166
|
return super()._run_task()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class DataUpdateRecordTask(SfDataToolingAPISupportedCommands):
|
|
170
|
+
class Options(SfDataToolingAPISupportedCommands.Options):
|
|
171
|
+
sobject: str = Field(
|
|
172
|
+
...,
|
|
173
|
+
description="API name of the Salesforce or Tooling API object that you're updating a record from.",
|
|
174
|
+
)
|
|
175
|
+
record_id: str = Field(None, description="ID of the record you’re updating.")
|
|
176
|
+
where: str = Field(
|
|
177
|
+
None,
|
|
178
|
+
description="List of <fieldName>=<value> pairs that identify the record you want to update.",
|
|
179
|
+
)
|
|
180
|
+
values: str = Field(
|
|
181
|
+
...,
|
|
182
|
+
description="Values for the flags in the form <fieldName>=<value>, separate multiple pairs with spaces.",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _init_task(self):
|
|
186
|
+
super()._init_task()
|
|
187
|
+
self.data_command += "update record"
|
|
188
|
+
|
|
189
|
+
def _init_options(self, kwargs):
|
|
190
|
+
super()._init_options(kwargs)
|
|
191
|
+
if self.parsed_options.sobject:
|
|
192
|
+
self.args.extend(["--sobject", self.parsed_options.sobject])
|
|
193
|
+
if self.parsed_options.record_id:
|
|
194
|
+
self.args.extend(["--record-id", self.parsed_options.record_id])
|
|
195
|
+
if self.parsed_options.where:
|
|
196
|
+
self.args.extend(["--where", self.parsed_options.where])
|
|
197
|
+
if self.parsed_options.values:
|
|
198
|
+
self.args.extend(["--values", self.parsed_options.values])
|
|
199
|
+
|
|
200
|
+
def _run_task(self):
|
|
201
|
+
return super()._run_task()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class DataGetRecordTask(SfDataToolingAPISupportedCommands):
|
|
205
|
+
class Options(SfDataToolingAPISupportedCommands.Options):
|
|
206
|
+
sobject: str = Field(
|
|
207
|
+
...,
|
|
208
|
+
description="API name of the Salesforce or Tooling API object that you're fetching a record from.",
|
|
209
|
+
)
|
|
210
|
+
record_id: str = Field(None, description="ID of the record you’re fetching.")
|
|
211
|
+
where: str = Field(
|
|
212
|
+
None,
|
|
213
|
+
description="List of <fieldName>=<value> pairs that identify the record you want to fetch.",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def _init_task(self):
|
|
217
|
+
super()._init_task()
|
|
218
|
+
self.data_command += "get record"
|
|
219
|
+
|
|
220
|
+
def _init_options(self, kwargs):
|
|
221
|
+
super()._init_options(kwargs)
|
|
222
|
+
if self.parsed_options.sobject:
|
|
223
|
+
self.args.extend(["--sobject", self.parsed_options.sobject])
|
|
224
|
+
if self.parsed_options.record_id:
|
|
225
|
+
self.args.extend(["--record-id", self.parsed_options.record_id])
|
|
226
|
+
if self.parsed_options.where:
|
|
227
|
+
self.args.extend(["--where", self.parsed_options.where])
|
|
228
|
+
|
|
229
|
+
def _run_task(self):
|
|
230
|
+
return super()._run_task()
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class DataQueryResumeTask(SfDataToolingAPISupportedCommands):
|
|
234
|
+
class Options(SfDataToolingAPISupportedCommands.Options):
|
|
235
|
+
bulk_query_id: str = Field(
|
|
236
|
+
...,
|
|
237
|
+
description="The 18-character ID of the bulk query to resume.",
|
|
238
|
+
)
|
|
239
|
+
result_format: str = Field(
|
|
240
|
+
None,
|
|
241
|
+
description="Format to display the results; the --json_output flag overrides this flag. Permissible values are: human, csv, json.",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def _init_task(self):
|
|
245
|
+
super()._init_task()
|
|
246
|
+
self.data_command += "query resume"
|
|
247
|
+
|
|
248
|
+
def _init_options(self, kwargs):
|
|
249
|
+
super()._init_options(kwargs)
|
|
250
|
+
if self.parsed_options.bulk_query_id:
|
|
251
|
+
self.args.extend(["--bulk-query-id", self.parsed_options.bulk_query_id])
|
|
252
|
+
if self.parsed_options.result_format:
|
|
253
|
+
self.args.extend(["--result-format", self.parsed_options.result_format])
|
|
254
|
+
|
|
255
|
+
def _run_task(self):
|
|
256
|
+
return super()._run_task()
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class DataDeleteBulkTask(SfDataCommands):
|
|
260
|
+
class Options(SfDataCommands.Options):
|
|
261
|
+
sobject: str = Field(
|
|
262
|
+
...,
|
|
263
|
+
description="The API name of the object for the bulk job.",
|
|
264
|
+
)
|
|
265
|
+
file: str = Field(
|
|
266
|
+
...,
|
|
267
|
+
description="The path to the CSV file that contains the IDs of the records to delete.",
|
|
268
|
+
)
|
|
269
|
+
wait: int = Field(
|
|
270
|
+
None,
|
|
271
|
+
description="The number of minutes to wait for the command to complete.",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def _init_task(self):
|
|
275
|
+
super()._init_task()
|
|
276
|
+
self.data_command += "delete bulk"
|
|
277
|
+
|
|
278
|
+
def _init_options(self, kwargs):
|
|
279
|
+
super()._init_options(kwargs)
|
|
280
|
+
if self.parsed_options.sobject:
|
|
281
|
+
self.args.extend(["--sobject", self.parsed_options.sobject])
|
|
282
|
+
if self.parsed_options.file:
|
|
283
|
+
self.args.extend(["--file", self.parsed_options.file])
|
|
284
|
+
if self.parsed_options.wait:
|
|
285
|
+
self.args.extend(["--wait", str(self.parsed_options.wait)])
|
|
286
|
+
|
|
287
|
+
def _run_task(self):
|
|
288
|
+
return super()._run_task()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class DataUpsertBulkTask(SfDataCommands):
|
|
292
|
+
class Options(SfDataCommands.Options):
|
|
293
|
+
sobject: str = Field(
|
|
294
|
+
...,
|
|
295
|
+
description="The API name of the object for the bulk job.",
|
|
296
|
+
)
|
|
297
|
+
file: str = Field(
|
|
298
|
+
...,
|
|
299
|
+
description="The path to the CSV file that contains the records to upsert.",
|
|
300
|
+
)
|
|
301
|
+
external_id_field: str = Field(
|
|
302
|
+
...,
|
|
303
|
+
description="The API name of the external ID field for the upsert.",
|
|
304
|
+
)
|
|
305
|
+
wait: int = Field(
|
|
306
|
+
None,
|
|
307
|
+
description="The number of minutes to wait for the command to complete.",
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def _init_task(self):
|
|
311
|
+
super()._init_task()
|
|
312
|
+
self.data_command += "upsert bulk"
|
|
313
|
+
|
|
314
|
+
def _init_options(self, kwargs):
|
|
315
|
+
super()._init_options(kwargs)
|
|
316
|
+
if self.parsed_options.sobject:
|
|
317
|
+
self.args.extend(["--sobject", self.parsed_options.sobject])
|
|
318
|
+
if self.parsed_options.file:
|
|
319
|
+
self.args.extend(["--file", self.parsed_options.file])
|
|
320
|
+
if self.parsed_options.external_id_field:
|
|
321
|
+
self.args.extend(
|
|
322
|
+
["--external-id-field", self.parsed_options.external_id_field]
|
|
323
|
+
)
|
|
324
|
+
if self.parsed_options.wait:
|
|
325
|
+
self.args.extend(["--wait", str(self.parsed_options.wait)])
|
|
326
|
+
|
|
327
|
+
def _run_task(self):
|
|
328
|
+
return super()._run_task()
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class DataImportTreeTask(SfDataCommands):
|
|
332
|
+
class Options(SfDataCommands.Options):
|
|
333
|
+
files: list = Field(
|
|
334
|
+
None,
|
|
335
|
+
description="A list of paths to sObject Tree API plan definition files.",
|
|
336
|
+
)
|
|
337
|
+
plan: str = Field(
|
|
338
|
+
None,
|
|
339
|
+
description="The path to a plan definition file.",
|
|
340
|
+
)
|
|
341
|
+
content_type_map: str = Field(
|
|
342
|
+
None,
|
|
343
|
+
description="A mapping of file extensions to content types.",
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
def _init_task(self):
|
|
347
|
+
super()._init_task()
|
|
348
|
+
self.data_command += "import tree"
|
|
349
|
+
|
|
350
|
+
def _init_options(self, kwargs):
|
|
351
|
+
super()._init_options(kwargs)
|
|
352
|
+
if self.parsed_options.files:
|
|
353
|
+
self.args.extend(["--files", ",".join(self.parsed_options.files)])
|
|
354
|
+
if self.parsed_options.plan:
|
|
355
|
+
self.args.extend(["--plan", self.parsed_options.plan])
|
|
356
|
+
if self.parsed_options.content_type_map:
|
|
357
|
+
self.args.extend(
|
|
358
|
+
["--content-type-map", self.parsed_options.content_type_map]
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def _run_task(self):
|
|
362
|
+
return super()._run_task()
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class DataExportTreeTask(SfDataCommands):
|
|
366
|
+
class Options(SfDataCommands.Options):
|
|
367
|
+
query: str = Field(
|
|
368
|
+
...,
|
|
369
|
+
description="A SOQL query that retrieves the records you want to export.",
|
|
370
|
+
)
|
|
371
|
+
plan: bool = Field(
|
|
372
|
+
False,
|
|
373
|
+
description="Generate a plan definition file.",
|
|
374
|
+
)
|
|
375
|
+
prefix: str = Field(
|
|
376
|
+
None,
|
|
377
|
+
description="The prefix for the exported data files.",
|
|
378
|
+
)
|
|
379
|
+
output_dir: str = Field(
|
|
380
|
+
None,
|
|
381
|
+
description="The directory to store the exported files.",
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def _init_task(self):
|
|
385
|
+
super()._init_task()
|
|
386
|
+
self.data_command += "export tree"
|
|
387
|
+
|
|
388
|
+
def _init_options(self, kwargs):
|
|
389
|
+
super()._init_options(kwargs)
|
|
390
|
+
if self.parsed_options.query:
|
|
391
|
+
self.args.extend(["--query", self.parsed_options.query])
|
|
392
|
+
if self.parsed_options.plan:
|
|
393
|
+
self.args.extend(["--plan"])
|
|
394
|
+
if self.parsed_options.prefix:
|
|
395
|
+
self.args.extend(["--prefix", self.parsed_options.prefix])
|
|
396
|
+
if self.parsed_options.output_dir:
|
|
397
|
+
self.args.extend(["--output-dir", self.parsed_options.output_dir])
|
|
398
|
+
|
|
399
|
+
def _run_task(self):
|
|
400
|
+
return super()._run_task()
|
|
@@ -23,6 +23,10 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
|
|
|
23
23
|
"description": "The description of the the new profile",
|
|
24
24
|
"required": False,
|
|
25
25
|
},
|
|
26
|
+
"skip_if_exists": {
|
|
27
|
+
"description": "Skip if the profile already exists in the target org. Defaults to True",
|
|
28
|
+
"required": False,
|
|
29
|
+
},
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
def _init_options(self, kwargs):
|
|
@@ -32,6 +36,7 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
|
|
|
32
36
|
"Either the name or the ID of the user license must be set."
|
|
33
37
|
)
|
|
34
38
|
self.license = self.options.get("license", "Salesforce")
|
|
39
|
+
self.sf = None
|
|
35
40
|
|
|
36
41
|
def _run_task(self):
|
|
37
42
|
|
|
@@ -39,6 +44,18 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
|
|
|
39
44
|
self.description = self.options.get("description") or ""
|
|
40
45
|
self.license_id = self.options.get("license_id")
|
|
41
46
|
|
|
47
|
+
profile_id = self._get_profile_id(self.name)
|
|
48
|
+
if profile_id:
|
|
49
|
+
if not self.options.get("skip_if_exists", True):
|
|
50
|
+
raise TaskOptionsError(
|
|
51
|
+
f"Profile '{self.name}' already exists with id: {profile_id}"
|
|
52
|
+
)
|
|
53
|
+
self.logger.info(
|
|
54
|
+
f"Profile '{self.name}' already exists with id: {profile_id}"
|
|
55
|
+
)
|
|
56
|
+
self.return_values = {"profile_id": profile_id}
|
|
57
|
+
return profile_id
|
|
58
|
+
|
|
42
59
|
if not self.license_id:
|
|
43
60
|
self.license_id = self._get_user_license_id(self.license)
|
|
44
61
|
|
|
@@ -48,22 +65,38 @@ class CreateBlankProfile(BaseSalesforceMetadataApiTask):
|
|
|
48
65
|
self.logger.info(f"Profile '{self.name}' created with id: {result}")
|
|
49
66
|
return result
|
|
50
67
|
|
|
68
|
+
def _get_profile_id(self, profile_name):
|
|
69
|
+
"""Returns the Id of a Profile from a given Name"""
|
|
70
|
+
res = self._query_sf(
|
|
71
|
+
f"SELECT Id, Name FROM Profile WHERE FullName = '{profile_name}' LIMIT 1"
|
|
72
|
+
)
|
|
73
|
+
if res["records"]:
|
|
74
|
+
return res["records"][0]["Id"]
|
|
75
|
+
|
|
76
|
+
self.logger.info(f"Profile name '{profile_name}' was not found.")
|
|
77
|
+
return None
|
|
78
|
+
|
|
51
79
|
def _get_user_license_id(self, license_name):
|
|
52
80
|
"""Returns the Id of a UserLicense from a given Name"""
|
|
53
|
-
|
|
54
|
-
self.project_config,
|
|
55
|
-
self.org_config,
|
|
56
|
-
api_version=self.org_config.latest_api_version,
|
|
57
|
-
base_url=None,
|
|
58
|
-
)
|
|
59
|
-
res = self.sf.query(
|
|
81
|
+
res = self._query_sf(
|
|
60
82
|
f"SELECT Id, Name FROM UserLicense WHERE Name = '{license_name}' LIMIT 1"
|
|
61
83
|
)
|
|
84
|
+
|
|
62
85
|
if res["records"]:
|
|
63
86
|
return res["records"][0]["Id"]
|
|
64
87
|
else:
|
|
65
88
|
raise TaskOptionsError(f"License name '{license_name}' was not found.")
|
|
66
89
|
|
|
90
|
+
def _query_sf(self, query):
|
|
91
|
+
self.sf = self.sf or get_simple_salesforce_connection(
|
|
92
|
+
self.project_config,
|
|
93
|
+
self.org_config,
|
|
94
|
+
api_version=self.org_config.latest_api_version,
|
|
95
|
+
base_url=None,
|
|
96
|
+
)
|
|
97
|
+
res = self.sf.query(query)
|
|
98
|
+
return res
|
|
99
|
+
|
|
67
100
|
def _get_api(self):
|
|
68
101
|
return self.api_class(
|
|
69
102
|
self,
|
|
@@ -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,125 @@ 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_skip_if_exists_false():
|
|
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
|
+
|
|
282
|
+
assert result == "10056000000VGjUAAW"
|
|
283
|
+
assert responses.calls[0].request.params == {
|
|
284
|
+
"q": "SELECT Id, Name FROM Profile WHERE FullName = 'Test Profile Name' LIMIT 1"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@responses.activate
|
|
289
|
+
def test_run_task_success_with_skip_if_exists_true():
|
|
290
|
+
query_url = "https://test.salesforce.com/services/data/v53.0/query/"
|
|
291
|
+
|
|
292
|
+
task = create_task(
|
|
293
|
+
CreateBlankProfile,
|
|
294
|
+
{
|
|
295
|
+
"license": "Foo",
|
|
296
|
+
"name": "Test Profile Name",
|
|
297
|
+
"description": "Have fun stormin da castle",
|
|
298
|
+
"skip_if_exists": False,
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
task.org_config._latest_api_version = "53.0"
|
|
302
|
+
|
|
303
|
+
responses.add(
|
|
304
|
+
responses.GET,
|
|
305
|
+
query_url,
|
|
306
|
+
json={
|
|
307
|
+
"done": True,
|
|
308
|
+
"totalSize": 1,
|
|
309
|
+
"records": [
|
|
310
|
+
{
|
|
311
|
+
"attributes": {
|
|
312
|
+
"type": "Profile",
|
|
313
|
+
"url": "/services/data/v53.0/sobjects/Profile/10056000000VGjUAAW",
|
|
314
|
+
},
|
|
315
|
+
"Id": "10056000000VGjUAAW",
|
|
316
|
+
"Name": "Test Profile Name",
|
|
317
|
+
}
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
with pytest.raises(TaskOptionsError) as e:
|
|
323
|
+
task._run_task()
|
|
324
|
+
assert "10056000000VGjUAAW" in str(e)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@responses.activate
|
|
328
|
+
def test_run_task_with_invalid_license():
|
|
329
|
+
query_url = "https://test.salesforce.com/services/data/v53.0/query/"
|
|
330
|
+
|
|
331
|
+
task = create_task(
|
|
332
|
+
CreateBlankProfile,
|
|
333
|
+
{
|
|
334
|
+
"license": "Foo",
|
|
335
|
+
"name": "Test Profile Name",
|
|
336
|
+
"description": "Have fun stormin da castle",
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
task.org_config._latest_api_version = "53.0"
|
|
340
|
+
|
|
341
|
+
responses.add(
|
|
342
|
+
responses.GET,
|
|
343
|
+
query_url,
|
|
344
|
+
json={
|
|
345
|
+
"done": True,
|
|
346
|
+
"totalSize": 1,
|
|
347
|
+
"records": [],
|
|
348
|
+
},
|
|
349
|
+
)
|
|
350
|
+
responses.add(
|
|
351
|
+
responses.GET,
|
|
352
|
+
query_url,
|
|
353
|
+
json={
|
|
354
|
+
"done": True,
|
|
355
|
+
"totalSize": 0,
|
|
356
|
+
"records": [],
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
responses.add(
|
|
360
|
+
responses.POST,
|
|
361
|
+
"https://test.salesforce.com/services/Soap/u/53.0/ORG_ID",
|
|
362
|
+
RESPONSE_SUCCESS,
|
|
363
|
+
)
|
|
364
|
+
with pytest.raises(TaskOptionsError) as e:
|
|
365
|
+
task._run_task()
|
|
366
|
+
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"]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cumulusci-plus
|
|
3
|
-
Version: 5.0.
|
|
3
|
+
Version: 5.0.20
|
|
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.20 (2025-09-05)
|
|
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
|
-
- Add
|
|
138
|
+
- Add Skil If Exists for create profile check by [@rupeshjSFDC](https://github.com/rupeshjSFDC) in [#65](https://github.com/jorgesolebur/CumulusCI/pull/65)
|
|
139
139
|
|
|
140
|
-
**Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.
|
|
140
|
+
**Full Changelog**: https://github.com/jorgesolebur/CumulusCI/compare/v5.0.19...v5.0.20
|