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
|
@@ -140,6 +140,10 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
140
140
|
"description": "Boolean: should we continue loading even after running into row errors? "
|
|
141
141
|
"Defaults to False."
|
|
142
142
|
},
|
|
143
|
+
"validate_only": {
|
|
144
|
+
"description": "Boolean: if True, only validate the generated mapping against the org schema without loading data. "
|
|
145
|
+
"Defaults to False."
|
|
146
|
+
},
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
def _validate_options(self):
|
|
@@ -160,6 +164,7 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
160
164
|
self.drop_missing_schema = process_bool_arg(
|
|
161
165
|
self.options.get("drop_missing_schema", False)
|
|
162
166
|
)
|
|
167
|
+
self.validate_only = process_bool_arg(self.options.get("validate_only", False))
|
|
163
168
|
|
|
164
169
|
loading_rules = process_list_arg(self.options.get("loading_rules")) or []
|
|
165
170
|
self.loading_rules = [Path(path) for path in loading_rules if path]
|
|
@@ -230,14 +235,19 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
230
235
|
def _run_task(self):
|
|
231
236
|
self.setup()
|
|
232
237
|
|
|
233
|
-
portions = PortionGenerator(
|
|
234
|
-
self.run_until.gap,
|
|
235
|
-
MIN_PORTION_SIZE,
|
|
236
|
-
MAX_PORTION_SIZE,
|
|
237
|
-
)
|
|
238
|
-
|
|
239
238
|
working_directory = self.options.get("working_directory")
|
|
240
239
|
with self.workingdir_or_tempdir(working_directory) as working_directory:
|
|
240
|
+
# Route to validation flow if validate_only is True
|
|
241
|
+
if self.validate_only:
|
|
242
|
+
return self._run_validation(working_directory)
|
|
243
|
+
|
|
244
|
+
# Normal data generation and loading flow
|
|
245
|
+
portions = PortionGenerator(
|
|
246
|
+
self.run_until.gap,
|
|
247
|
+
MIN_PORTION_SIZE,
|
|
248
|
+
MAX_PORTION_SIZE,
|
|
249
|
+
)
|
|
250
|
+
|
|
241
251
|
self._setup_channels_and_queues(working_directory)
|
|
242
252
|
self.logger.info(f"Working directory is {working_directory}")
|
|
243
253
|
|
|
@@ -544,32 +554,20 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
544
554
|
|
|
545
555
|
template_dir = Path(working_directory) / "template_1"
|
|
546
556
|
template_dir.mkdir()
|
|
547
|
-
# changes here should often be reflected in
|
|
548
|
-
# data_generator_opts and data_loader_opts
|
|
549
557
|
|
|
550
558
|
channel_decl = self.channel_configs[0]
|
|
551
559
|
|
|
552
|
-
plugin_options = {
|
|
553
|
-
"pid": "0",
|
|
554
|
-
"big_ids": "True",
|
|
555
|
-
}
|
|
556
560
|
# if it's efficient to do the whole load in one go, let's just do that.
|
|
557
561
|
if self.run_until.gap < MIN_PORTION_SIZE:
|
|
558
562
|
num_records = self.run_until.gap
|
|
559
563
|
else:
|
|
560
564
|
num_records = 1 # smallest possible batch to get to parallelizing fast
|
|
565
|
+
|
|
566
|
+
batch_options = self._prepare_initial_batch_options(num_records)
|
|
561
567
|
results = self._generate_and_load_batch(
|
|
562
568
|
template_dir,
|
|
563
569
|
channel_decl.org_config,
|
|
564
|
-
|
|
565
|
-
"generator_yaml": self.options.get("recipe"),
|
|
566
|
-
"num_records": num_records,
|
|
567
|
-
"num_records_tablename": self.run_until.sobject_name or COUNT_REPS,
|
|
568
|
-
"loading_rules": self.loading_rules,
|
|
569
|
-
"vars": channel_decl.merge_recipe_options(self.recipe_options),
|
|
570
|
-
"plugin_options": plugin_options,
|
|
571
|
-
"bulk_mode": self.bulk_mode,
|
|
572
|
-
},
|
|
570
|
+
batch_options,
|
|
573
571
|
)
|
|
574
572
|
self.update_running_totals_from_load_step_results(results)
|
|
575
573
|
|
|
@@ -595,9 +593,19 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
595
593
|
|
|
596
594
|
return template_dir, wd.relevant_sobjects()
|
|
597
595
|
|
|
598
|
-
def
|
|
599
|
-
|
|
600
|
-
|
|
596
|
+
def _run_generate_and_load_subtask(
|
|
597
|
+
self, tempdir, org_config, options, validate_only=False
|
|
598
|
+
):
|
|
599
|
+
"""Run GenerateAndLoadDataFromYaml subtask with given options.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
tempdir: Working directory for generated files
|
|
603
|
+
org_config: Org configuration
|
|
604
|
+
options: Options dict for the subtask
|
|
605
|
+
validate_only: If True, only validate mapping without loading
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
dict: Subtask return values
|
|
601
609
|
"""
|
|
602
610
|
options = {
|
|
603
611
|
**options,
|
|
@@ -605,6 +613,7 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
605
613
|
"set_recently_viewed": False,
|
|
606
614
|
"ignore_row_errors": self.ignore_row_errors,
|
|
607
615
|
"drop_missing_schema": self.drop_missing_schema,
|
|
616
|
+
"validate_only": validate_only,
|
|
608
617
|
}
|
|
609
618
|
subtask_config = TaskConfig({"options": options})
|
|
610
619
|
subtask = GenerateAndLoadDataFromYaml(
|
|
@@ -616,7 +625,73 @@ class Snowfakery(BaseSalesforceApiTask):
|
|
|
616
625
|
stepnum=self.stepnum,
|
|
617
626
|
)
|
|
618
627
|
subtask()
|
|
619
|
-
return subtask.return_values
|
|
628
|
+
return subtask.return_values
|
|
629
|
+
|
|
630
|
+
def _prepare_initial_batch_options(self, num_records: int) -> dict:
|
|
631
|
+
"""Prepare options for initial data generation batch.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
num_records: Number of records to generate
|
|
635
|
+
|
|
636
|
+
Returns:
|
|
637
|
+
dict: Options for GenerateAndLoadDataFromYaml subtask
|
|
638
|
+
"""
|
|
639
|
+
channel_decl = self.channel_configs[0]
|
|
640
|
+
|
|
641
|
+
plugin_options = {
|
|
642
|
+
"pid": "0",
|
|
643
|
+
"big_ids": "True",
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
"generator_yaml": self.options.get("recipe"),
|
|
648
|
+
"num_records": num_records,
|
|
649
|
+
"num_records_tablename": self.run_until.sobject_name or COUNT_REPS,
|
|
650
|
+
"loading_rules": self.loading_rules,
|
|
651
|
+
"vars": channel_decl.merge_recipe_options(self.recipe_options),
|
|
652
|
+
"plugin_options": plugin_options,
|
|
653
|
+
"bulk_mode": self.bulk_mode,
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
def _run_validation(self, working_directory: Path):
|
|
657
|
+
"""Run validation flow: generate minimal data and validate mapping.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
working_directory: Working directory for generated files
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
dict: return_values with validation_result
|
|
664
|
+
"""
|
|
665
|
+
template_dir = Path(working_directory) / "template_validation"
|
|
666
|
+
template_dir.mkdir()
|
|
667
|
+
|
|
668
|
+
channel_decl = self.channel_configs[0]
|
|
669
|
+
|
|
670
|
+
# Prepare options for validation
|
|
671
|
+
batch_options = self._prepare_initial_batch_options(num_records=1)
|
|
672
|
+
|
|
673
|
+
# Run generation and validation
|
|
674
|
+
subtask_return_values = self._run_generate_and_load_subtask(
|
|
675
|
+
template_dir,
|
|
676
|
+
channel_decl.org_config,
|
|
677
|
+
batch_options,
|
|
678
|
+
validate_only=True,
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
# Set return values with validation result
|
|
682
|
+
self.return_values = {
|
|
683
|
+
"validation_result": subtask_return_values["validation_result"]
|
|
684
|
+
}
|
|
685
|
+
return self.return_values
|
|
686
|
+
|
|
687
|
+
def _generate_and_load_batch(self, tempdir, org_config, options) -> dict:
|
|
688
|
+
"""Before the "full" dataload starts we do a single batch to
|
|
689
|
+
load singletons.
|
|
690
|
+
"""
|
|
691
|
+
subtask_return_values = self._run_generate_and_load_subtask(
|
|
692
|
+
tempdir, org_config, options, validate_only=False
|
|
693
|
+
)
|
|
694
|
+
return subtask_return_values["load_results"][0]
|
|
620
695
|
|
|
621
696
|
def _cleanup_object_tables(self, engine, metadata):
|
|
622
697
|
"""Delete all tables that do not relate to id->OID mapping"""
|
|
@@ -250,3 +250,162 @@ class TestGenerateAndLoadData:
|
|
|
250
250
|
)
|
|
251
251
|
task()
|
|
252
252
|
assert list(Path(t).glob("*"))
|
|
253
|
+
|
|
254
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._dataload")
|
|
255
|
+
@mock.patch(
|
|
256
|
+
"cumulusci.tasks.bulkdata.generate_and_load_data.validate_and_inject_mapping"
|
|
257
|
+
)
|
|
258
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._datagen")
|
|
259
|
+
def test_validate_only_mode(self, mock_datagen, mock_validate, _dataload):
|
|
260
|
+
"""Test that validate_only mode validates without loading data"""
|
|
261
|
+
from cumulusci.tasks.bulkdata.mapping_parser import ValidationResult
|
|
262
|
+
|
|
263
|
+
mapping_file = os.path.join(os.path.dirname(__file__), "mapping_vanilla_sf.yml")
|
|
264
|
+
|
|
265
|
+
# Mock ValidationResult
|
|
266
|
+
validation_result = ValidationResult()
|
|
267
|
+
mock_validate.return_value = validation_result
|
|
268
|
+
|
|
269
|
+
task = _make_task(
|
|
270
|
+
GenerateAndLoadData,
|
|
271
|
+
{
|
|
272
|
+
"options": {
|
|
273
|
+
"num_records": 12,
|
|
274
|
+
"mapping": mapping_file,
|
|
275
|
+
"data_generation_task": "cumulusci.tasks.bulkdata.tests.dummy_data_factory.GenerateDummyData",
|
|
276
|
+
"validate_only": True,
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
task()
|
|
282
|
+
|
|
283
|
+
# Verify data generation was called (to create mapping)
|
|
284
|
+
mock_datagen.assert_called_once()
|
|
285
|
+
|
|
286
|
+
# Verify validation was called
|
|
287
|
+
mock_validate.assert_called_once()
|
|
288
|
+
|
|
289
|
+
# Verify load was NOT called
|
|
290
|
+
_dataload.assert_not_called()
|
|
291
|
+
|
|
292
|
+
# Verify return values contain validation_result
|
|
293
|
+
assert "validation_result" in task.return_values
|
|
294
|
+
assert task.return_values["validation_result"] == validation_result
|
|
295
|
+
|
|
296
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._dataload")
|
|
297
|
+
@mock.patch(
|
|
298
|
+
"cumulusci.tasks.bulkdata.generate_and_load_data.validate_and_inject_mapping"
|
|
299
|
+
)
|
|
300
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._datagen")
|
|
301
|
+
def test_validate_only_with_errors(self, mock_datagen, mock_validate, _dataload):
|
|
302
|
+
"""Test that validate_only mode returns errors without raising exception"""
|
|
303
|
+
from cumulusci.tasks.bulkdata.mapping_parser import ValidationResult
|
|
304
|
+
|
|
305
|
+
mapping_file = os.path.join(os.path.dirname(__file__), "mapping_vanilla_sf.yml")
|
|
306
|
+
|
|
307
|
+
# Mock ValidationResult with errors
|
|
308
|
+
validation_result = ValidationResult()
|
|
309
|
+
validation_result.add_error("Test error: Field does not exist")
|
|
310
|
+
validation_result.add_warning("Test warning: Field has no permissions")
|
|
311
|
+
mock_validate.return_value = validation_result
|
|
312
|
+
|
|
313
|
+
task = _make_task(
|
|
314
|
+
GenerateAndLoadData,
|
|
315
|
+
{
|
|
316
|
+
"options": {
|
|
317
|
+
"num_records": 12,
|
|
318
|
+
"mapping": mapping_file,
|
|
319
|
+
"data_generation_task": "cumulusci.tasks.bulkdata.tests.dummy_data_factory.GenerateDummyData",
|
|
320
|
+
"validate_only": True,
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
# Should not raise exception even with errors
|
|
326
|
+
task()
|
|
327
|
+
|
|
328
|
+
# Verify data generation was called
|
|
329
|
+
mock_datagen.assert_called_once()
|
|
330
|
+
|
|
331
|
+
# Verify validation was called
|
|
332
|
+
mock_validate.assert_called_once()
|
|
333
|
+
|
|
334
|
+
# Verify load was NOT called
|
|
335
|
+
_dataload.assert_not_called()
|
|
336
|
+
|
|
337
|
+
# Verify return values contain validation_result with errors
|
|
338
|
+
assert "validation_result" in task.return_values
|
|
339
|
+
assert task.return_values["validation_result"].has_errors()
|
|
340
|
+
assert len(task.return_values["validation_result"].errors) == 1
|
|
341
|
+
assert len(task.return_values["validation_result"].warnings) == 1
|
|
342
|
+
|
|
343
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._dataload")
|
|
344
|
+
def test_validate_only_false_loads_data(self, _dataload):
|
|
345
|
+
"""Test that validate_only=False performs normal data loading"""
|
|
346
|
+
mapping_file = os.path.join(os.path.dirname(__file__), "mapping_vanilla_sf.yml")
|
|
347
|
+
|
|
348
|
+
task = _make_task(
|
|
349
|
+
GenerateAndLoadData,
|
|
350
|
+
{
|
|
351
|
+
"options": {
|
|
352
|
+
"num_records": 12,
|
|
353
|
+
"mapping": mapping_file,
|
|
354
|
+
"data_generation_task": "cumulusci.tasks.bulkdata.tests.dummy_data_factory.GenerateDummyData",
|
|
355
|
+
"validate_only": False,
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
task()
|
|
361
|
+
|
|
362
|
+
# Verify load WAS called
|
|
363
|
+
_dataload.assert_called_once()
|
|
364
|
+
|
|
365
|
+
# Verify return values contain load_results, not validation_result
|
|
366
|
+
assert "load_results" in task.return_values
|
|
367
|
+
assert "validation_result" not in task.return_values
|
|
368
|
+
|
|
369
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._dataload")
|
|
370
|
+
@mock.patch(
|
|
371
|
+
"cumulusci.tasks.bulkdata.generate_and_load_data.validate_and_inject_mapping"
|
|
372
|
+
)
|
|
373
|
+
@mock.patch("cumulusci.tasks.bulkdata.GenerateAndLoadData._datagen")
|
|
374
|
+
def test_validate_only_with_working_directory(
|
|
375
|
+
self, mock_datagen, mock_validate, _dataload
|
|
376
|
+
):
|
|
377
|
+
"""Test that validate_only respects working_directory option"""
|
|
378
|
+
from cumulusci.tasks.bulkdata.mapping_parser import ValidationResult
|
|
379
|
+
|
|
380
|
+
mapping_file = os.path.join(os.path.dirname(__file__), "mapping_vanilla_sf.yml")
|
|
381
|
+
|
|
382
|
+
validation_result = ValidationResult()
|
|
383
|
+
mock_validate.return_value = validation_result
|
|
384
|
+
|
|
385
|
+
with TemporaryDirectory() as t:
|
|
386
|
+
task = _make_task(
|
|
387
|
+
GenerateAndLoadData,
|
|
388
|
+
{
|
|
389
|
+
"options": {
|
|
390
|
+
"num_records": 12,
|
|
391
|
+
"mapping": mapping_file,
|
|
392
|
+
"data_generation_task": "cumulusci.tasks.bulkdata.tests.dummy_data_factory.GenerateDummyData",
|
|
393
|
+
"validate_only": True,
|
|
394
|
+
"working_directory": t,
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
task()
|
|
400
|
+
|
|
401
|
+
# Verify data generation was called
|
|
402
|
+
mock_datagen.assert_called_once()
|
|
403
|
+
|
|
404
|
+
# Verify validation was called
|
|
405
|
+
mock_validate.assert_called_once()
|
|
406
|
+
|
|
407
|
+
# Verify load was NOT called
|
|
408
|
+
_dataload.assert_not_called()
|
|
409
|
+
|
|
410
|
+
# Verify working directory was used (should have generated files)
|
|
411
|
+
assert list(Path(t).glob("*"))
|