cumulusci-plus 5.0.0__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 -0
- cumulusci/__init__.py +22 -0
- cumulusci/__main__.py +3 -0
- cumulusci/cli/__init__.py +0 -0
- cumulusci/cli/cci.py +244 -0
- cumulusci/cli/error.py +125 -0
- cumulusci/cli/flow.py +185 -0
- cumulusci/cli/logger.py +72 -0
- cumulusci/cli/org.py +692 -0
- cumulusci/cli/plan.py +181 -0
- cumulusci/cli/project.py +391 -0
- cumulusci/cli/robot.py +116 -0
- cumulusci/cli/runtime.py +190 -0
- cumulusci/cli/service.py +521 -0
- cumulusci/cli/task.py +295 -0
- cumulusci/cli/tests/__init__.py +0 -0
- cumulusci/cli/tests/test_cci.py +545 -0
- cumulusci/cli/tests/test_error.py +170 -0
- cumulusci/cli/tests/test_flow.py +276 -0
- cumulusci/cli/tests/test_logger.py +25 -0
- cumulusci/cli/tests/test_org.py +1438 -0
- cumulusci/cli/tests/test_plan.py +245 -0
- cumulusci/cli/tests/test_project.py +235 -0
- cumulusci/cli/tests/test_robot.py +177 -0
- cumulusci/cli/tests/test_runtime.py +197 -0
- cumulusci/cli/tests/test_service.py +853 -0
- cumulusci/cli/tests/test_task.py +266 -0
- cumulusci/cli/tests/test_ui.py +310 -0
- cumulusci/cli/tests/test_utils.py +122 -0
- cumulusci/cli/tests/utils.py +52 -0
- cumulusci/cli/ui.py +234 -0
- cumulusci/cli/utils.py +150 -0
- cumulusci/conftest.py +181 -0
- cumulusci/core/__init__.py +0 -0
- cumulusci/core/config/BaseConfig.py +5 -0
- cumulusci/core/config/BaseTaskFlowConfig.py +5 -0
- cumulusci/core/config/OrgConfig.py +5 -0
- cumulusci/core/config/ScratchOrgConfig.py +5 -0
- cumulusci/core/config/__init__.py +125 -0
- cumulusci/core/config/base_config.py +111 -0
- cumulusci/core/config/base_task_flow_config.py +82 -0
- cumulusci/core/config/marketing_cloud_service_config.py +83 -0
- cumulusci/core/config/oauth2_service_config.py +17 -0
- cumulusci/core/config/org_config.py +604 -0
- cumulusci/core/config/project_config.py +782 -0
- cumulusci/core/config/scratch_org_config.py +251 -0
- cumulusci/core/config/sfdx_org_config.py +220 -0
- cumulusci/core/config/tests/_test_config_backwards_compatibility.py +33 -0
- cumulusci/core/config/tests/test_config.py +1895 -0
- cumulusci/core/config/tests/test_config_expensive.py +839 -0
- cumulusci/core/config/tests/test_config_util.py +91 -0
- cumulusci/core/config/universal_config.py +88 -0
- cumulusci/core/config/util.py +18 -0
- cumulusci/core/datasets.py +303 -0
- cumulusci/core/debug.py +33 -0
- cumulusci/core/dependencies/__init__.py +55 -0
- cumulusci/core/dependencies/base.py +561 -0
- cumulusci/core/dependencies/dependencies.py +273 -0
- cumulusci/core/dependencies/github.py +177 -0
- cumulusci/core/dependencies/github_resolvers.py +244 -0
- cumulusci/core/dependencies/resolvers.py +580 -0
- cumulusci/core/dependencies/tests/__init__.py +0 -0
- cumulusci/core/dependencies/tests/conftest.py +385 -0
- cumulusci/core/dependencies/tests/test_dependencies.py +950 -0
- cumulusci/core/dependencies/tests/test_github.py +83 -0
- cumulusci/core/dependencies/tests/test_resolvers.py +1027 -0
- cumulusci/core/dependencies/utils.py +13 -0
- cumulusci/core/enums.py +11 -0
- cumulusci/core/exceptions.py +311 -0
- cumulusci/core/flowrunner.py +888 -0
- cumulusci/core/github.py +665 -0
- cumulusci/core/keychain/__init__.py +24 -0
- cumulusci/core/keychain/base_project_keychain.py +441 -0
- cumulusci/core/keychain/encrypted_file_project_keychain.py +945 -0
- cumulusci/core/keychain/environment_project_keychain.py +7 -0
- cumulusci/core/keychain/serialization.py +152 -0
- cumulusci/core/keychain/subprocess_keychain.py +24 -0
- cumulusci/core/keychain/tests/conftest.py +50 -0
- cumulusci/core/keychain/tests/test_base_project_keychain.py +299 -0
- cumulusci/core/keychain/tests/test_encrypted_file_project_keychain.py +1228 -0
- cumulusci/core/metadeploy/__init__.py +0 -0
- cumulusci/core/metadeploy/api.py +88 -0
- cumulusci/core/metadeploy/plans.py +25 -0
- cumulusci/core/metadeploy/tests/test_api.py +276 -0
- cumulusci/core/runtime.py +115 -0
- cumulusci/core/sfdx.py +162 -0
- cumulusci/core/source/__init__.py +16 -0
- cumulusci/core/source/github.py +50 -0
- cumulusci/core/source/local_folder.py +35 -0
- cumulusci/core/source_transforms/__init__.py +0 -0
- cumulusci/core/source_transforms/tests/test_transforms.py +1091 -0
- cumulusci/core/source_transforms/transforms.py +532 -0
- cumulusci/core/tasks.py +404 -0
- cumulusci/core/template_utils.py +59 -0
- cumulusci/core/tests/__init__.py +0 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml +215 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml +199 -0
- cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_read_explicit_extract_declaration.yaml +3 -0
- cumulusci/core/tests/fake_remote_repo/cumulusci.yml +32 -0
- cumulusci/core/tests/fake_remote_repo/tasks/directory/example_2.py +6 -0
- cumulusci/core/tests/fake_remote_repo/tasks/example.py +43 -0
- cumulusci/core/tests/fake_remote_repo_2/cumulusci.yml +11 -0
- cumulusci/core/tests/fake_remote_repo_2/tasks/example_3.py +6 -0
- cumulusci/core/tests/test_datasets_e2e.py +386 -0
- cumulusci/core/tests/test_exceptions.py +11 -0
- cumulusci/core/tests/test_flowrunner.py +836 -0
- cumulusci/core/tests/test_github.py +942 -0
- cumulusci/core/tests/test_sfdx.py +138 -0
- cumulusci/core/tests/test_source.py +678 -0
- cumulusci/core/tests/test_tasks.py +262 -0
- cumulusci/core/tests/test_utils.py +141 -0
- cumulusci/core/tests/test_utils_merge_config.py +276 -0
- cumulusci/core/tests/test_versions.py +76 -0
- cumulusci/core/tests/untrusted_repo_child/cumulusci.yml +7 -0
- cumulusci/core/tests/untrusted_repo_child/tasks/untrusted_child.py +6 -0
- cumulusci/core/tests/untrusted_repo_parent/cumulusci.yml +26 -0
- cumulusci/core/tests/untrusted_repo_parent/tasks/untrusted_parent.py +6 -0
- cumulusci/core/tests/utils.py +116 -0
- cumulusci/core/tests/yaml/global.yaml +0 -0
- cumulusci/core/utils.py +402 -0
- cumulusci/core/versions.py +149 -0
- cumulusci/cumulusci.yml +1621 -0
- cumulusci/files/admin_profile.xml +20 -0
- cumulusci/files/delete_excludes.txt +424 -0
- cumulusci/files/templates/project/README.md +12 -0
- cumulusci/files/templates/project/cumulusci.yml +63 -0
- cumulusci/files/templates/project/dot-gitignore +60 -0
- cumulusci/files/templates/project/mapping.yml +45 -0
- cumulusci/files/templates/project/scratch_def.json +25 -0
- cumulusci/oauth/__init__.py +0 -0
- cumulusci/oauth/client.py +400 -0
- cumulusci/oauth/exceptions.py +9 -0
- cumulusci/oauth/salesforce.py +95 -0
- cumulusci/oauth/tests/__init__.py +0 -0
- cumulusci/oauth/tests/cassettes/test_get_device_code.yaml +22 -0
- cumulusci/oauth/tests/cassettes/test_get_device_oauth_token.yaml +74 -0
- cumulusci/oauth/tests/test_client.py +308 -0
- cumulusci/oauth/tests/test_salesforce.py +46 -0
- cumulusci/plugins/__init__.py +3 -0
- cumulusci/plugins/plugin_base.py +93 -0
- cumulusci/plugins/plugin_loader.py +59 -0
- cumulusci/robotframework/CumulusCI.py +340 -0
- cumulusci/robotframework/CumulusCI.robot +7 -0
- cumulusci/robotframework/Performance.py +165 -0
- cumulusci/robotframework/Salesforce.py +936 -0
- cumulusci/robotframework/Salesforce.robot +192 -0
- cumulusci/robotframework/SalesforceAPI.py +416 -0
- cumulusci/robotframework/SalesforcePlaywright.py +220 -0
- cumulusci/robotframework/SalesforcePlaywright.robot +40 -0
- cumulusci/robotframework/__init__.py +2 -0
- cumulusci/robotframework/base_library.py +39 -0
- cumulusci/robotframework/faker_mixin.py +89 -0
- cumulusci/robotframework/form_handlers.py +222 -0
- cumulusci/robotframework/javascript/cci_init.js +34 -0
- cumulusci/robotframework/javascript/cumulusci.js +4 -0
- cumulusci/robotframework/locator_manager.py +197 -0
- cumulusci/robotframework/locators_56.py +88 -0
- cumulusci/robotframework/locators_57.py +5 -0
- cumulusci/robotframework/pageobjects/BasePageObjects.py +433 -0
- cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +246 -0
- cumulusci/robotframework/pageobjects/PageObjectLibrary.py +45 -0
- cumulusci/robotframework/pageobjects/PageObjects.py +351 -0
- cumulusci/robotframework/pageobjects/__init__.py +12 -0
- cumulusci/robotframework/pageobjects/baseobjects.py +120 -0
- cumulusci/robotframework/perftests/short/collection_perf.robot +105 -0
- cumulusci/robotframework/tests/CustomObjectTestPage.py +10 -0
- cumulusci/robotframework/tests/FooTestPage.py +8 -0
- cumulusci/robotframework/tests/cumulusci/base.robot +40 -0
- cumulusci/robotframework/tests/cumulusci/bulkdata.robot +38 -0
- cumulusci/robotframework/tests/cumulusci/communities.robot +57 -0
- cumulusci/robotframework/tests/cumulusci/datagen.robot +84 -0
- cumulusci/robotframework/tests/salesforce/TestLibraryA.py +24 -0
- cumulusci/robotframework/tests/salesforce/TestLibraryB.py +20 -0
- cumulusci/robotframework/tests/salesforce/TestListener.py +93 -0
- cumulusci/robotframework/tests/salesforce/api.robot +178 -0
- cumulusci/robotframework/tests/salesforce/browsers.robot +143 -0
- cumulusci/robotframework/tests/salesforce/classic.robot +51 -0
- cumulusci/robotframework/tests/salesforce/create_contact.robot +59 -0
- cumulusci/robotframework/tests/salesforce/faker.robot +68 -0
- cumulusci/robotframework/tests/salesforce/forms.robot +172 -0
- cumulusci/robotframework/tests/salesforce/label_locator.robot +244 -0
- cumulusci/robotframework/tests/salesforce/labels.html +33 -0
- cumulusci/robotframework/tests/salesforce/locators.robot +149 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/base_pageobjects.robot +100 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/example_page_object.py +25 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/listing_page.robot +115 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/objectmanager.robot +74 -0
- cumulusci/robotframework/tests/salesforce/pageobjects/pageobjects.robot +171 -0
- cumulusci/robotframework/tests/salesforce/performance.robot +109 -0
- cumulusci/robotframework/tests/salesforce/playwright/javascript_keywords.robot +33 -0
- cumulusci/robotframework/tests/salesforce/playwright/open_test_browser.robot +48 -0
- cumulusci/robotframework/tests/salesforce/playwright/playwright.robot +24 -0
- cumulusci/robotframework/tests/salesforce/playwright/ui.robot +32 -0
- cumulusci/robotframework/tests/salesforce/populate.robot +89 -0
- cumulusci/robotframework/tests/salesforce/test_testlistener.py +37 -0
- cumulusci/robotframework/tests/salesforce/ui.robot +361 -0
- cumulusci/robotframework/tests/test_cumulusci_library.py +304 -0
- cumulusci/robotframework/tests/test_locator_manager.py +158 -0
- cumulusci/robotframework/tests/test_pageobjects.py +291 -0
- cumulusci/robotframework/tests/test_performance.py +38 -0
- cumulusci/robotframework/tests/test_salesforce.py +79 -0
- cumulusci/robotframework/tests/test_salesforce_locators.py +73 -0
- cumulusci/robotframework/tests/test_template_util.py +53 -0
- cumulusci/robotframework/tests/test_utils.py +106 -0
- cumulusci/robotframework/utils.py +283 -0
- cumulusci/salesforce_api/__init__.py +0 -0
- cumulusci/salesforce_api/exceptions.py +23 -0
- cumulusci/salesforce_api/filterable_objects.py +96 -0
- cumulusci/salesforce_api/mc_soap_envelopes.py +89 -0
- cumulusci/salesforce_api/metadata.py +721 -0
- cumulusci/salesforce_api/org_schema.py +571 -0
- cumulusci/salesforce_api/org_schema_models.py +226 -0
- cumulusci/salesforce_api/package_install.py +265 -0
- cumulusci/salesforce_api/package_zip.py +301 -0
- cumulusci/salesforce_api/rest_deploy.py +148 -0
- cumulusci/salesforce_api/retrieve_profile_api.py +301 -0
- cumulusci/salesforce_api/soap_envelopes.py +177 -0
- cumulusci/salesforce_api/tests/__init__.py +0 -0
- cumulusci/salesforce_api/tests/metadata_test_strings.py +24 -0
- cumulusci/salesforce_api/tests/test_metadata.py +1015 -0
- cumulusci/salesforce_api/tests/test_package_install.py +219 -0
- cumulusci/salesforce_api/tests/test_package_zip.py +380 -0
- cumulusci/salesforce_api/tests/test_rest_deploy.py +264 -0
- cumulusci/salesforce_api/tests/test_retrieve_profile_api.py +337 -0
- cumulusci/salesforce_api/tests/test_utils.py +124 -0
- cumulusci/salesforce_api/utils.py +51 -0
- cumulusci/schema/cumulusci.jsonschema.json +782 -0
- cumulusci/tasks/__init__.py +0 -0
- cumulusci/tasks/apex/__init__.py +0 -0
- cumulusci/tasks/apex/anon.py +157 -0
- cumulusci/tasks/apex/batch.py +180 -0
- cumulusci/tasks/apex/testrunner.py +835 -0
- cumulusci/tasks/apex/tests/cassettes/ManualEditTestApexIntegrationTests.test_run_tests__integration_test.yaml +703 -0
- cumulusci/tasks/apex/tests/test_apex_tasks.py +1558 -0
- cumulusci/tasks/base_source_control_task.py +17 -0
- cumulusci/tasks/bulkdata/__init__.py +15 -0
- cumulusci/tasks/bulkdata/base_generate_data_task.py +96 -0
- cumulusci/tasks/bulkdata/dates.py +97 -0
- cumulusci/tasks/bulkdata/delete.py +156 -0
- cumulusci/tasks/bulkdata/extract.py +441 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/calculate_dependencies.py +117 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +123 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py +49 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +283 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +142 -0
- cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_synthesize_extract_declarations.py +575 -0
- cumulusci/tasks/bulkdata/factory_utils.py +134 -0
- cumulusci/tasks/bulkdata/generate.py +4 -0
- cumulusci/tasks/bulkdata/generate_and_load_data.py +232 -0
- cumulusci/tasks/bulkdata/generate_and_load_data_from_yaml.py +19 -0
- cumulusci/tasks/bulkdata/generate_from_yaml.py +183 -0
- cumulusci/tasks/bulkdata/generate_mapping.py +434 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/dependency_map.py +169 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/extract_mapping_file_generator.py +45 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/generate_mapping_from_declarations.py +121 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/load_mapping_file_generator.py +127 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_generator_post_processes.py +53 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_transforms.py +139 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_extract_mapping_from_declarations.py +135 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_load_mapping_from_declarations.py +330 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_generator_post_processes.py +60 -0
- cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_transforms.py +188 -0
- cumulusci/tasks/bulkdata/load.py +1196 -0
- cumulusci/tasks/bulkdata/mapping_parser.py +811 -0
- cumulusci/tasks/bulkdata/query_transformers.py +264 -0
- cumulusci/tasks/bulkdata/select_utils.py +792 -0
- cumulusci/tasks/bulkdata/snowfakery.py +753 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py +478 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_run_until.py +141 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_working_directory.py +53 -0
- cumulusci/tasks/bulkdata/snowfakery_utils/subtask_configurator.py +64 -0
- cumulusci/tasks/bulkdata/step.py +1242 -0
- cumulusci/tasks/bulkdata/tests/__init__.py +0 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_random_strategy.yaml +147 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_annoy_strategy.yaml +123 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy.yaml +313 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy_bulk.yaml +550 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_strategy.yaml +175 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_standard_strategy.yaml +147 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__multiple_needed.yaml +69 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__none_needed.yaml +22 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__one_needed.yaml +24 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_snowfakery_query_salesforce.yaml +25 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpdatesIntegrationTests.test_updates_task.yaml +80 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_simple_upsert__rest.yaml +270 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert__rest.yaml +267 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field__rest.yaml +369 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field_rest__duplicate_error.yaml +204 -0
- cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_fields__bulk.yaml +675 -0
- cumulusci/tasks/bulkdata/tests/dummy_data_factory.py +36 -0
- cumulusci/tasks/bulkdata/tests/integration_test_utils.py +49 -0
- cumulusci/tasks/bulkdata/tests/mapping-oid.yml +87 -0
- cumulusci/tasks/bulkdata/tests/mapping_after.yml +38 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly.yml +34 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly_incomplete.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_poly_wrong.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_strategy.yml +20 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_number.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_strategy.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__non_float.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_missing_priority_fields.yml +22 -0
- cumulusci/tasks/bulkdata/tests/mapping_select_no_priority_fields.yml +18 -0
- cumulusci/tasks/bulkdata/tests/mapping_simple.yml +27 -0
- cumulusci/tasks/bulkdata/tests/mapping_v1.yml +28 -0
- cumulusci/tasks/bulkdata/tests/mapping_v2.yml +21 -0
- cumulusci/tasks/bulkdata/tests/mapping_v3.yml +32 -0
- cumulusci/tasks/bulkdata/tests/mapping_vanilla_sf.yml +69 -0
- cumulusci/tasks/bulkdata/tests/mock_data_factory_without_mapping.py +12 -0
- cumulusci/tasks/bulkdata/tests/person_accounts.yml +23 -0
- cumulusci/tasks/bulkdata/tests/person_accounts_minimal.yml +15 -0
- cumulusci/tasks/bulkdata/tests/recordtypes.yml +8 -0
- cumulusci/tasks/bulkdata/tests/recordtypes_2.yml +6 -0
- cumulusci/tasks/bulkdata/tests/recordtypes_with_ispersontype.yml +8 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/child/child2.yml +3 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/child.yml +4 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/gen_npsp_standard_objects.recipe.yml +89 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/include_parent.yml +3 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/npsp_standard_objects_macros.yml +34 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/options.recipe.yml +6 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/query_snowfakery.recipe.yml +16 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/sf_standard_object_macros.yml +83 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.load.yml +2 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.recipe.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_2.load.yml +5 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.load.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.recipe.yml +12 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels_2.load.yml +13 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/unique_values.recipe.yml +4 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert.recipe.yml +23 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert_2.recipe.yml +29 -0
- cumulusci/tasks/bulkdata/tests/snowfakery/upsert_before.yml +10 -0
- cumulusci/tasks/bulkdata/tests/test_base_generate_data_tasks.py +61 -0
- cumulusci/tasks/bulkdata/tests/test_dates.py +99 -0
- cumulusci/tasks/bulkdata/tests/test_delete.py +404 -0
- cumulusci/tasks/bulkdata/tests/test_extract.py +1311 -0
- cumulusci/tasks/bulkdata/tests/test_factory_utils.py +55 -0
- cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +252 -0
- cumulusci/tasks/bulkdata/tests/test_generate_from_snowfakery_task.py +343 -0
- cumulusci/tasks/bulkdata/tests/test_generatemapping.py +1039 -0
- cumulusci/tasks/bulkdata/tests/test_load.py +3175 -0
- cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +1658 -0
- cumulusci/tasks/bulkdata/tests/test_query_db__joins_self_lookups.yml +12 -0
- cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups.yml +26 -0
- cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups_select.yml +48 -0
- cumulusci/tasks/bulkdata/tests/test_select.py +171 -0
- cumulusci/tasks/bulkdata/tests/test_select_utils.py +1057 -0
- cumulusci/tasks/bulkdata/tests/test_snowfakery.py +1153 -0
- cumulusci/tasks/bulkdata/tests/test_step.py +3957 -0
- cumulusci/tasks/bulkdata/tests/test_updates.py +513 -0
- cumulusci/tasks/bulkdata/tests/test_upsert.py +1015 -0
- cumulusci/tasks/bulkdata/tests/test_utils.py +158 -0
- cumulusci/tasks/bulkdata/tests/testdata.db +0 -0
- cumulusci/tasks/bulkdata/tests/update_describe.py +50 -0
- cumulusci/tasks/bulkdata/tests/update_person_accounts.yml +23 -0
- cumulusci/tasks/bulkdata/tests/utils.py +114 -0
- cumulusci/tasks/bulkdata/update_data.py +260 -0
- cumulusci/tasks/bulkdata/upsert_utils.py +130 -0
- cumulusci/tasks/bulkdata/utils.py +249 -0
- cumulusci/tasks/command.py +178 -0
- cumulusci/tasks/connectedapp.py +186 -0
- cumulusci/tasks/create_package_version.py +778 -0
- cumulusci/tasks/datadictionary.py +745 -0
- cumulusci/tasks/dx_convert_from.py +26 -0
- cumulusci/tasks/github/__init__.py +17 -0
- cumulusci/tasks/github/base.py +16 -0
- cumulusci/tasks/github/commit_status.py +13 -0
- cumulusci/tasks/github/merge.py +11 -0
- cumulusci/tasks/github/publish.py +11 -0
- cumulusci/tasks/github/pull_request.py +11 -0
- cumulusci/tasks/github/release.py +11 -0
- cumulusci/tasks/github/release_report.py +11 -0
- cumulusci/tasks/github/tag.py +11 -0
- cumulusci/tasks/github/tests/__init__.py +0 -0
- cumulusci/tasks/github/tests/test_util.py +202 -0
- cumulusci/tasks/github/tests/test_vcs_migration.py +44 -0
- cumulusci/tasks/github/tests/util_github_api.py +666 -0
- cumulusci/tasks/github/util.py +252 -0
- cumulusci/tasks/marketing_cloud/__init__.py +0 -0
- cumulusci/tasks/marketing_cloud/api.py +188 -0
- cumulusci/tasks/marketing_cloud/base.py +38 -0
- cumulusci/tasks/marketing_cloud/deploy.py +345 -0
- cumulusci/tasks/marketing_cloud/get_user_info.py +40 -0
- cumulusci/tasks/marketing_cloud/mc_constants.py +1 -0
- cumulusci/tasks/marketing_cloud/tests/__init__.py +0 -0
- cumulusci/tasks/marketing_cloud/tests/conftest.py +46 -0
- cumulusci/tasks/marketing_cloud/tests/expected-payload.json +110 -0
- cumulusci/tasks/marketing_cloud/tests/test_api.py +97 -0
- cumulusci/tasks/marketing_cloud/tests/test_api_soap_envelopes.py +145 -0
- cumulusci/tasks/marketing_cloud/tests/test_base.py +14 -0
- cumulusci/tasks/marketing_cloud/tests/test_deploy.py +400 -0
- cumulusci/tasks/marketing_cloud/tests/test_get_user_info.py +141 -0
- cumulusci/tasks/marketing_cloud/tests/validation-response.json +39 -0
- cumulusci/tasks/metadata/__init__.py +0 -0
- cumulusci/tasks/metadata/ee_src.py +94 -0
- cumulusci/tasks/metadata/managed_src.py +100 -0
- cumulusci/tasks/metadata/metadata_map.yml +868 -0
- cumulusci/tasks/metadata/modify.py +99 -0
- cumulusci/tasks/metadata/package.py +684 -0
- cumulusci/tasks/metadata/tests/__init__.py +0 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/.hidden/.keep +0 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/destructiveChanges.xml +9 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package.xml +9 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package_install_uninstall.xml +11 -0
- cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/reports/namespace__TestFolder/TestReport.report +3 -0
- cumulusci/tasks/metadata/tests/sample_package.xml +9 -0
- cumulusci/tasks/metadata/tests/test_ee_src.py +112 -0
- cumulusci/tasks/metadata/tests/test_managed_src.py +111 -0
- cumulusci/tasks/metadata/tests/test_modify.py +123 -0
- cumulusci/tasks/metadata/tests/test_package.py +476 -0
- cumulusci/tasks/metadata_etl/__init__.py +29 -0
- cumulusci/tasks/metadata_etl/base.py +436 -0
- cumulusci/tasks/metadata_etl/duplicate_rules.py +24 -0
- cumulusci/tasks/metadata_etl/field_sets.py +70 -0
- cumulusci/tasks/metadata_etl/help_text.py +92 -0
- cumulusci/tasks/metadata_etl/layouts.py +550 -0
- cumulusci/tasks/metadata_etl/objects.py +68 -0
- cumulusci/tasks/metadata_etl/permissions.py +167 -0
- cumulusci/tasks/metadata_etl/picklists.py +221 -0
- cumulusci/tasks/metadata_etl/remote_site_settings.py +99 -0
- cumulusci/tasks/metadata_etl/sharing.py +138 -0
- cumulusci/tasks/metadata_etl/tests/test_base.py +512 -0
- cumulusci/tasks/metadata_etl/tests/test_duplicate_rules.py +22 -0
- cumulusci/tasks/metadata_etl/tests/test_field_sets.py +156 -0
- cumulusci/tasks/metadata_etl/tests/test_help_text.py +387 -0
- cumulusci/tasks/metadata_etl/tests/test_ip_ranges.py +85 -0
- cumulusci/tasks/metadata_etl/tests/test_layouts.py +858 -0
- cumulusci/tasks/metadata_etl/tests/test_objects.py +236 -0
- cumulusci/tasks/metadata_etl/tests/test_permissions.py +223 -0
- cumulusci/tasks/metadata_etl/tests/test_picklists.py +547 -0
- cumulusci/tasks/metadata_etl/tests/test_remote_site_settings.py +46 -0
- cumulusci/tasks/metadata_etl/tests/test_sharing.py +333 -0
- cumulusci/tasks/metadata_etl/tests/test_value_sets.py +298 -0
- cumulusci/tasks/metadata_etl/value_sets.py +106 -0
- cumulusci/tasks/metadeploy.py +393 -0
- cumulusci/tasks/metaxml.py +88 -0
- cumulusci/tasks/preflight/__init__.py +0 -0
- cumulusci/tasks/preflight/dataset_load.py +49 -0
- cumulusci/tasks/preflight/licenses.py +86 -0
- cumulusci/tasks/preflight/packages.py +14 -0
- cumulusci/tasks/preflight/permsets.py +23 -0
- cumulusci/tasks/preflight/recordtypes.py +16 -0
- cumulusci/tasks/preflight/retrieve_tasks.py +30 -0
- cumulusci/tasks/preflight/settings.py +77 -0
- cumulusci/tasks/preflight/sobjects.py +202 -0
- cumulusci/tasks/preflight/tests/test_dataset_load.py +85 -0
- cumulusci/tasks/preflight/tests/test_licenses.py +174 -0
- cumulusci/tasks/preflight/tests/test_packages.py +14 -0
- cumulusci/tasks/preflight/tests/test_permset_preflights.py +51 -0
- cumulusci/tasks/preflight/tests/test_recordtypes.py +30 -0
- cumulusci/tasks/preflight/tests/test_retrieve_tasks.py +62 -0
- cumulusci/tasks/preflight/tests/test_settings.py +130 -0
- cumulusci/tasks/preflight/tests/test_sobjects.py +231 -0
- cumulusci/tasks/push/README.md +59 -0
- cumulusci/tasks/push/__init__.py +0 -0
- cumulusci/tasks/push/push_api.py +659 -0
- cumulusci/tasks/push/pushfails.py +136 -0
- cumulusci/tasks/push/tasks.py +476 -0
- cumulusci/tasks/push/tests/conftest.py +263 -0
- cumulusci/tasks/push/tests/test_push_api.py +951 -0
- cumulusci/tasks/push/tests/test_push_tasks.py +659 -0
- cumulusci/tasks/release_notes/README.md +63 -0
- cumulusci/tasks/release_notes/__init__.py +0 -0
- cumulusci/tasks/release_notes/exceptions.py +5 -0
- cumulusci/tasks/release_notes/generator.py +137 -0
- cumulusci/tasks/release_notes/parser.py +232 -0
- cumulusci/tasks/release_notes/provider.py +44 -0
- cumulusci/tasks/release_notes/task.py +300 -0
- cumulusci/tasks/release_notes/tests/__init__.py +0 -0
- cumulusci/tasks/release_notes/tests/change_notes/full/example1.md +17 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/1.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/2.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/multi/3.txt +1 -0
- cumulusci/tasks/release_notes/tests/change_notes/single/1.txt +1 -0
- cumulusci/tasks/release_notes/tests/test_generator.py +582 -0
- cumulusci/tasks/release_notes/tests/test_parser.py +867 -0
- cumulusci/tasks/release_notes/tests/test_provider.py +512 -0
- cumulusci/tasks/release_notes/tests/test_task.py +461 -0
- cumulusci/tasks/release_notes/tests/utils.py +153 -0
- cumulusci/tasks/robotframework/__init__.py +3 -0
- cumulusci/tasks/robotframework/debugger/DebugListener.py +100 -0
- cumulusci/tasks/robotframework/debugger/__init__.py +10 -0
- cumulusci/tasks/robotframework/debugger/model.py +87 -0
- cumulusci/tasks/robotframework/debugger/ui.py +259 -0
- cumulusci/tasks/robotframework/libdoc.py +269 -0
- cumulusci/tasks/robotframework/robotframework.py +392 -0
- cumulusci/tasks/robotframework/stylesheet.css +130 -0
- cumulusci/tasks/robotframework/template.html +109 -0
- cumulusci/tasks/robotframework/tests/TestLibrary.py +18 -0
- cumulusci/tasks/robotframework/tests/TestPageObjects.py +31 -0
- cumulusci/tasks/robotframework/tests/TestResource.robot +8 -0
- cumulusci/tasks/robotframework/tests/failing_tests.robot +16 -0
- cumulusci/tasks/robotframework/tests/performance.robot +23 -0
- cumulusci/tasks/robotframework/tests/test_browser_proxies.py +137 -0
- cumulusci/tasks/robotframework/tests/test_debugger.py +360 -0
- cumulusci/tasks/robotframework/tests/test_robot_parallel.py +141 -0
- cumulusci/tasks/robotframework/tests/test_robotframework.py +860 -0
- cumulusci/tasks/salesforce/BaseRetrieveMetadata.py +58 -0
- cumulusci/tasks/salesforce/BaseSalesforceApiTask.py +45 -0
- cumulusci/tasks/salesforce/BaseSalesforceMetadataApiTask.py +18 -0
- cumulusci/tasks/salesforce/BaseSalesforceTask.py +4 -0
- cumulusci/tasks/salesforce/BaseUninstallMetadata.py +41 -0
- cumulusci/tasks/salesforce/CreateCommunity.py +124 -0
- cumulusci/tasks/salesforce/CreatePackage.py +29 -0
- cumulusci/tasks/salesforce/Deploy.py +240 -0
- cumulusci/tasks/salesforce/DeployBundles.py +88 -0
- cumulusci/tasks/salesforce/DescribeMetadataTypes.py +26 -0
- cumulusci/tasks/salesforce/EnsureRecordTypes.py +202 -0
- cumulusci/tasks/salesforce/GetInstalledPackages.py +8 -0
- cumulusci/tasks/salesforce/ListCommunities.py +40 -0
- cumulusci/tasks/salesforce/ListCommunityTemplates.py +19 -0
- cumulusci/tasks/salesforce/PublishCommunity.py +62 -0
- cumulusci/tasks/salesforce/RetrievePackaged.py +41 -0
- cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py +82 -0
- cumulusci/tasks/salesforce/RetrieveUnpackaged.py +36 -0
- cumulusci/tasks/salesforce/SOQLQuery.py +39 -0
- cumulusci/tasks/salesforce/UninstallLocal.py +15 -0
- cumulusci/tasks/salesforce/UninstallLocalBundles.py +28 -0
- cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py +58 -0
- cumulusci/tasks/salesforce/UninstallPackage.py +32 -0
- cumulusci/tasks/salesforce/UninstallPackaged.py +56 -0
- cumulusci/tasks/salesforce/UpdateAdminProfile.py +8 -0
- cumulusci/tasks/salesforce/__init__.py +79 -0
- cumulusci/tasks/salesforce/activate_flow.py +74 -0
- cumulusci/tasks/salesforce/check_components.py +324 -0
- cumulusci/tasks/salesforce/composite.py +142 -0
- cumulusci/tasks/salesforce/create_permission_sets.py +35 -0
- cumulusci/tasks/salesforce/custom_settings.py +134 -0
- cumulusci/tasks/salesforce/custom_settings_wait.py +132 -0
- cumulusci/tasks/salesforce/enable_prediction.py +107 -0
- cumulusci/tasks/salesforce/insert_record.py +40 -0
- cumulusci/tasks/salesforce/install_package_version.py +242 -0
- cumulusci/tasks/salesforce/license_preflights.py +8 -0
- cumulusci/tasks/salesforce/network_member_group.py +178 -0
- cumulusci/tasks/salesforce/nonsourcetracking.py +228 -0
- cumulusci/tasks/salesforce/org_settings.py +193 -0
- cumulusci/tasks/salesforce/package_upload.py +328 -0
- cumulusci/tasks/salesforce/profiles.py +74 -0
- cumulusci/tasks/salesforce/promote_package_version.py +376 -0
- cumulusci/tasks/salesforce/retrieve_profile.py +195 -0
- cumulusci/tasks/salesforce/salesforce_files.py +244 -0
- cumulusci/tasks/salesforce/sourcetracking.py +507 -0
- cumulusci/tasks/salesforce/tests/__init__.py +3 -0
- cumulusci/tasks/salesforce/tests/test_CreateCommunity.py +278 -0
- cumulusci/tasks/salesforce/tests/test_CreatePackage.py +22 -0
- cumulusci/tasks/salesforce/tests/test_Deploy.py +470 -0
- cumulusci/tasks/salesforce/tests/test_DeployBundles.py +76 -0
- cumulusci/tasks/salesforce/tests/test_EnsureRecordTypes.py +345 -0
- cumulusci/tasks/salesforce/tests/test_ListCommunities.py +84 -0
- cumulusci/tasks/salesforce/tests/test_ListCommunityTemplates.py +49 -0
- cumulusci/tasks/salesforce/tests/test_PackageUpload.py +547 -0
- cumulusci/tasks/salesforce/tests/test_ProfileGrantAllAccess.py +699 -0
- cumulusci/tasks/salesforce/tests/test_PublishCommunity.py +181 -0
- cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +24 -0
- cumulusci/tasks/salesforce/tests/test_RetrieveReportsAndDashboards.py +56 -0
- cumulusci/tasks/salesforce/tests/test_RetrieveUnpackaged.py +21 -0
- cumulusci/tasks/salesforce/tests/test_SOQLQuery.py +30 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocal.py +15 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocalBundles.py +19 -0
- cumulusci/tasks/salesforce/tests/test_UninstallLocalNamespacedBundles.py +22 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackage.py +19 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +66 -0
- cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +127 -0
- cumulusci/tasks/salesforce/tests/test_activate_flow.py +132 -0
- cumulusci/tasks/salesforce/tests/test_base_tasks.py +110 -0
- cumulusci/tasks/salesforce/tests/test_check_components.py +445 -0
- cumulusci/tasks/salesforce/tests/test_composite.py +250 -0
- cumulusci/tasks/salesforce/tests/test_create_permission_sets.py +41 -0
- cumulusci/tasks/salesforce/tests/test_custom_settings.py +227 -0
- cumulusci/tasks/salesforce/tests/test_custom_settings_wait.py +174 -0
- cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py +18 -0
- cumulusci/tasks/salesforce/tests/test_enable_prediction.py +240 -0
- cumulusci/tasks/salesforce/tests/test_insert_record.py +110 -0
- cumulusci/tasks/salesforce/tests/test_install_package_version.py +464 -0
- cumulusci/tasks/salesforce/tests/test_network_member_group.py +444 -0
- cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py +235 -0
- cumulusci/tasks/salesforce/tests/test_org_settings.py +407 -0
- cumulusci/tasks/salesforce/tests/test_profiles.py +202 -0
- cumulusci/tasks/salesforce/tests/test_retrieve_profile.py +287 -0
- cumulusci/tasks/salesforce/tests/test_salesforce_files.py +228 -0
- cumulusci/tasks/salesforce/tests/test_sourcetracking.py +350 -0
- cumulusci/tasks/salesforce/tests/test_trigger_handlers.py +300 -0
- cumulusci/tasks/salesforce/tests/test_update_dependencies.py +509 -0
- cumulusci/tasks/salesforce/tests/util.py +79 -0
- cumulusci/tasks/salesforce/trigger_handlers.py +119 -0
- cumulusci/tasks/salesforce/uninstall_packaged_incremental.py +136 -0
- cumulusci/tasks/salesforce/update_dependencies.py +290 -0
- cumulusci/tasks/salesforce/update_profile.py +339 -0
- cumulusci/tasks/salesforce/users/permsets.py +227 -0
- cumulusci/tasks/salesforce/users/photos.py +162 -0
- cumulusci/tasks/salesforce/users/tests/photo.mock.txt +1 -0
- cumulusci/tasks/salesforce/users/tests/test_permsets.py +950 -0
- cumulusci/tasks/salesforce/users/tests/test_photos.py +373 -0
- cumulusci/tasks/sample_data/capture_sample_data.py +77 -0
- cumulusci/tasks/sample_data/load_sample_data.py +85 -0
- cumulusci/tasks/sample_data/test_capture_sample_data.py +117 -0
- cumulusci/tasks/sample_data/test_load_sample_data.py +121 -0
- cumulusci/tasks/sfdx.py +83 -0
- cumulusci/tasks/tests/__init__.py +1 -0
- cumulusci/tasks/tests/conftest.py +30 -0
- cumulusci/tasks/tests/test_command.py +129 -0
- cumulusci/tasks/tests/test_connectedapp.py +236 -0
- cumulusci/tasks/tests/test_create_package_version.py +847 -0
- cumulusci/tasks/tests/test_datadictionary.py +1575 -0
- cumulusci/tasks/tests/test_dx_convert_from.py +60 -0
- cumulusci/tasks/tests/test_metadeploy.py +624 -0
- cumulusci/tasks/tests/test_metaxml.py +99 -0
- cumulusci/tasks/tests/test_promote_package_version.py +488 -0
- cumulusci/tasks/tests/test_pushfails.py +96 -0
- cumulusci/tasks/tests/test_salesforce.py +72 -0
- cumulusci/tasks/tests/test_sfdx.py +105 -0
- cumulusci/tasks/tests/test_util.py +207 -0
- cumulusci/tasks/util.py +261 -0
- cumulusci/tasks/vcs/__init__.py +19 -0
- cumulusci/tasks/vcs/commit_status.py +58 -0
- cumulusci/tasks/vcs/create_commit_status.py +37 -0
- cumulusci/tasks/vcs/download_extract.py +199 -0
- cumulusci/tasks/vcs/merge.py +298 -0
- cumulusci/tasks/vcs/publish.py +207 -0
- cumulusci/tasks/vcs/pull_request.py +9 -0
- cumulusci/tasks/vcs/release.py +134 -0
- cumulusci/tasks/vcs/release_report.py +105 -0
- cumulusci/tasks/vcs/tag.py +31 -0
- cumulusci/tasks/vcs/tests/github/test_commit_status.py +196 -0
- cumulusci/tasks/vcs/tests/github/test_download_extract.py +896 -0
- cumulusci/tasks/vcs/tests/github/test_merge.py +1118 -0
- cumulusci/tasks/vcs/tests/github/test_publish.py +823 -0
- cumulusci/tasks/vcs/tests/github/test_pull_request.py +29 -0
- cumulusci/tasks/vcs/tests/github/test_release.py +390 -0
- cumulusci/tasks/vcs/tests/github/test_release_report.py +109 -0
- cumulusci/tasks/vcs/tests/github/test_tag.py +90 -0
- cumulusci/tasks/vlocity/exceptions.py +2 -0
- cumulusci/tasks/vlocity/tests/test_vlocity.py +283 -0
- cumulusci/tasks/vlocity/vlocity.py +342 -0
- cumulusci/tests/__init__.py +1 -0
- cumulusci/tests/cassettes/GET_sobjects_Account_PersonAccount_describe.yaml +18 -0
- cumulusci/tests/cassettes/TestIntegrationInfrastructure.test_integration_tests.yaml +19 -0
- cumulusci/tests/pytest_plugins/pytest_sf_orgconnect.py +307 -0
- cumulusci/tests/pytest_plugins/pytest_sf_vcr.py +275 -0
- cumulusci/tests/pytest_plugins/pytest_sf_vcr_serializer.py +160 -0
- cumulusci/tests/pytest_plugins/pytest_typeguard.py +5 -0
- cumulusci/tests/pytest_plugins/test_vcr_string_compressor.py +49 -0
- cumulusci/tests/pytest_plugins/vcr_string_compressor.py +97 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Account_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Case_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Contact_describe.yaml +4838 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Custom__c_describe.yaml +242 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Event_describe.yaml +19 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Global_describe.yaml +1338 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Lead_describe.yaml +18 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_OpportunityContactRole_describe.yaml +34 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Opportunity_describe.yaml +1261 -0
- cumulusci/tests/shared_cassettes/GET_sobjects_Organization.yaml +49 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfoList_xml.tpl +15 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfo_xml.tpl +13 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_insert_xml.tpl +24 -0
- cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_upsert_xml.tpl +25 -0
- cumulusci/tests/test_entry_points.py +20 -0
- cumulusci/tests/test_integration_infrastructure.py +131 -0
- cumulusci/tests/test_main.py +9 -0
- cumulusci/tests/test_schema.py +32 -0
- cumulusci/tests/test_utils.py +657 -0
- cumulusci/tests/test_vcr_serializer.py +134 -0
- cumulusci/tests/uncompressed_cassette.yaml +83 -0
- cumulusci/tests/util.py +344 -0
- cumulusci/utils/__init__.py +731 -0
- cumulusci/utils/classutils.py +9 -0
- cumulusci/utils/collections.py +32 -0
- cumulusci/utils/deprecation.py +11 -0
- cumulusci/utils/encryption.py +31 -0
- cumulusci/utils/fileutils.py +295 -0
- cumulusci/utils/git.py +142 -0
- cumulusci/utils/http/multi_request.py +214 -0
- cumulusci/utils/http/requests_utils.py +103 -0
- cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +32 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_composite_parallel_salesforce.yaml +65 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_errors.yaml +24 -0
- cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_reference_ids.yaml +49 -0
- cumulusci/utils/http/tests/test_multi_request.py +255 -0
- cumulusci/utils/iterators.py +21 -0
- cumulusci/utils/logging.py +128 -0
- cumulusci/utils/metaprogramming.py +10 -0
- cumulusci/utils/options.py +138 -0
- cumulusci/utils/parallel/queries_in_parallel/run_queries_in_parallel.py +29 -0
- cumulusci/utils/parallel/queries_in_parallel/tests/test_run_queries_in_parallel.py +50 -0
- cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +238 -0
- cumulusci/utils/parallel/task_worker_queues/parallel_worker_queue.py +243 -0
- cumulusci/utils/parallel/task_worker_queues/tests/test_parallel_worker.py +353 -0
- cumulusci/utils/salesforce/count_sobjects.py +46 -0
- cumulusci/utils/salesforce/soql.py +17 -0
- cumulusci/utils/salesforce/tests/cassettes/ManualEdit_TestCountSObjects.test_count_sobjects__network_errors.yaml +23 -0
- cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects__errors.yaml +33 -0
- cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects_simple.yaml +29 -0
- cumulusci/utils/salesforce/tests/test_count_sobjects.py +29 -0
- cumulusci/utils/salesforce/tests/test_soql.py +30 -0
- cumulusci/utils/tests/cassettes/ManualEditTestDescribeOrg.test_minimal_schema.yaml +36 -0
- cumulusci/utils/tests/cassettes/ManualEdit_test_describe_to_sql.yaml +191 -0
- cumulusci/utils/tests/test_fileutils.py +284 -0
- cumulusci/utils/tests/test_git.py +85 -0
- cumulusci/utils/tests/test_logging.py +70 -0
- cumulusci/utils/tests/test_option_parsing.py +188 -0
- cumulusci/utils/tests/test_org_schema.py +691 -0
- cumulusci/utils/tests/test_org_schema_models.py +79 -0
- cumulusci/utils/tests/test_waiting.py +25 -0
- cumulusci/utils/version_strings.py +391 -0
- cumulusci/utils/waiting.py +42 -0
- cumulusci/utils/xml/__init__.py +91 -0
- cumulusci/utils/xml/metadata_tree.py +299 -0
- cumulusci/utils/xml/robot_xml.py +114 -0
- cumulusci/utils/xml/salesforce_encoding.py +100 -0
- cumulusci/utils/xml/test/test_metadata_tree.py +251 -0
- cumulusci/utils/xml/test/test_salesforce_encoding.py +173 -0
- cumulusci/utils/yaml/cumulusci_yml.py +401 -0
- cumulusci/utils/yaml/model_parser.py +156 -0
- cumulusci/utils/yaml/safer_loader.py +74 -0
- cumulusci/utils/yaml/tests/bad_cci.yml +5 -0
- cumulusci/utils/yaml/tests/cassettes/TestCumulusciYml.test_validate_url__with_errors.yaml +20 -0
- cumulusci/utils/yaml/tests/test_cumulusci_yml.py +286 -0
- cumulusci/utils/yaml/tests/test_model_parser.py +175 -0
- cumulusci/utils/yaml/tests/test_safer_loader.py +88 -0
- cumulusci/utils/ziputils.py +61 -0
- cumulusci/vcs/base.py +143 -0
- cumulusci/vcs/bootstrap.py +272 -0
- cumulusci/vcs/github/__init__.py +24 -0
- cumulusci/vcs/github/adapter.py +689 -0
- cumulusci/vcs/github/release_notes/generator.py +219 -0
- cumulusci/vcs/github/release_notes/parser.py +151 -0
- cumulusci/vcs/github/release_notes/provider.py +143 -0
- cumulusci/vcs/github/service.py +569 -0
- cumulusci/vcs/github/tests/test_adapter.py +138 -0
- cumulusci/vcs/github/tests/test_service.py +408 -0
- cumulusci/vcs/models.py +586 -0
- cumulusci/vcs/tests/conftest.py +41 -0
- cumulusci/vcs/tests/dummy_service.py +241 -0
- cumulusci/vcs/tests/test_vcs_base.py +687 -0
- cumulusci/vcs/tests/test_vcs_bootstrap.py +727 -0
- cumulusci/vcs/utils/__init__.py +31 -0
- cumulusci/vcs/vcs_source.py +287 -0
- cumulusci_plus-5.0.0.dist-info/METADATA +145 -0
- cumulusci_plus-5.0.0.dist-info/RECORD +744 -0
- cumulusci_plus-5.0.0.dist-info/WHEEL +4 -0
- cumulusci_plus-5.0.0.dist-info/entry_points.txt +3 -0
- cumulusci_plus-5.0.0.dist-info/licenses/AUTHORS.rst +41 -0
- cumulusci_plus-5.0.0.dist-info/licenses/LICENSE +30 -0
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import typing as T
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from itertools import cycle
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from tempfile import TemporaryDirectory
|
|
8
|
+
from threading import Lock, Thread
|
|
9
|
+
from unittest import mock
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
import yaml
|
|
13
|
+
from sqlalchemy import MetaData, create_engine
|
|
14
|
+
|
|
15
|
+
from cumulusci.core import exceptions as exc
|
|
16
|
+
from cumulusci.core.config import OrgConfig
|
|
17
|
+
from cumulusci.tasks.bulkdata.snowfakery import (
|
|
18
|
+
RunningTotals,
|
|
19
|
+
Snowfakery,
|
|
20
|
+
SnowfakeryWorkingDirectory,
|
|
21
|
+
)
|
|
22
|
+
from cumulusci.tasks.bulkdata.tests.integration_test_utils import ensure_accounts
|
|
23
|
+
from cumulusci.tasks.bulkdata.tests.utils import _make_task
|
|
24
|
+
from cumulusci.tasks.salesforce.BaseSalesforceApiTask import BaseSalesforceApiTask
|
|
25
|
+
from cumulusci.tests.util import DummyKeychain, DummyOrgConfig
|
|
26
|
+
from cumulusci.utils.parallel.task_worker_queues.tests.test_parallel_worker import (
|
|
27
|
+
DelaySpawner,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
ensure_accounts = ensure_accounts # fixes 4 lint errors at once. Don't hate the player, hate the game.
|
|
31
|
+
|
|
32
|
+
simple_salesforce_yaml = (
|
|
33
|
+
Path(__file__).parent / "snowfakery/simple_snowfakery.recipe.yml"
|
|
34
|
+
)
|
|
35
|
+
sample_yaml = Path(__file__).parent / "snowfakery/gen_npsp_standard_objects.recipe.yml"
|
|
36
|
+
query_yaml = Path(__file__).parent / "snowfakery/query_snowfakery.recipe.yml"
|
|
37
|
+
|
|
38
|
+
original_refresh_token = OrgConfig.refresh_oauth_token
|
|
39
|
+
|
|
40
|
+
FAKE_LOAD_RESULTS = (
|
|
41
|
+
{
|
|
42
|
+
"Insert Account": {
|
|
43
|
+
"sobject": "Account",
|
|
44
|
+
"record_type": None,
|
|
45
|
+
"status": "Success",
|
|
46
|
+
"records_processed": 2,
|
|
47
|
+
"total_row_errors": 0,
|
|
48
|
+
},
|
|
49
|
+
"Insert Contact": {
|
|
50
|
+
"sobject": "Contact",
|
|
51
|
+
"record_type": None,
|
|
52
|
+
"status": "Success",
|
|
53
|
+
"records_processed": 2,
|
|
54
|
+
"total_row_errors": 0,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"Insert Account": {
|
|
59
|
+
"sobject": "Account",
|
|
60
|
+
"record_type": None,
|
|
61
|
+
"status": "Success",
|
|
62
|
+
"records_processed": 3,
|
|
63
|
+
"total_row_errors": 0,
|
|
64
|
+
},
|
|
65
|
+
"Insert Contact": {
|
|
66
|
+
"sobject": "Contact",
|
|
67
|
+
"record_type": None,
|
|
68
|
+
"status": "Success",
|
|
69
|
+
"records_processed": 3,
|
|
70
|
+
"total_row_errors": 0,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def table_values(connection, table):
|
|
77
|
+
query = f"select * from {table.name}"
|
|
78
|
+
values = [val for val in connection.execute(query)]
|
|
79
|
+
return values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class FakeLoadData(BaseSalesforceApiTask):
|
|
83
|
+
"""Simulates load results without doing a real load."""
|
|
84
|
+
|
|
85
|
+
# these are all used as mutable class variables
|
|
86
|
+
mock_calls: list # similar to how a mock.Mock() object works
|
|
87
|
+
fake_return_values: T.Iterator
|
|
88
|
+
fake_exception_on_request = -1
|
|
89
|
+
lock = Lock()
|
|
90
|
+
|
|
91
|
+
# Manipulating "self" from a mock side-effect is a challenge.
|
|
92
|
+
# So we need a "real function"
|
|
93
|
+
def __call__(self, *args, **kwargs):
|
|
94
|
+
"""Like the __call__ of _run_task, but also capture calls
|
|
95
|
+
in a normal mock_values structure."""
|
|
96
|
+
|
|
97
|
+
with self.lock: # the code below looks thread-safe but better safe than sorry
|
|
98
|
+
|
|
99
|
+
# tasks usually aren't called twice after being instantiated
|
|
100
|
+
# that would usually be a bug.
|
|
101
|
+
assert self not in self.mock_calls
|
|
102
|
+
self.__class__.mock_calls.append(self)
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
len(self.__class__.mock_calls)
|
|
106
|
+
== self.__class__.fake_exception_on_request
|
|
107
|
+
):
|
|
108
|
+
raise AssertionError("You asked me to raise an exception")
|
|
109
|
+
|
|
110
|
+
# get the values that the Snowfakery task asked us to load and
|
|
111
|
+
# remember them for later inspection.
|
|
112
|
+
self.values_loaded = db_values_from_db_url(self.options["database_url"])
|
|
113
|
+
|
|
114
|
+
# TODO:
|
|
115
|
+
#
|
|
116
|
+
# Parse the mapping and then count matching objects to return
|
|
117
|
+
# more realistic values.
|
|
118
|
+
# return a fake return value so Snowfakery loader doesn't get confused
|
|
119
|
+
self.return_values = {"step_results": next(self.fake_return_values)}
|
|
120
|
+
|
|
121
|
+
# using mutable class variables is not something I would usually do
|
|
122
|
+
# because it is not thread safe, but the test intrinsically uses
|
|
123
|
+
# threads and therefore is not thread safe in general.
|
|
124
|
+
#
|
|
125
|
+
# Furthermore, attempts to use a closure instead of mutable class
|
|
126
|
+
# variables just doesn't work because of how Snowfakery instantiates
|
|
127
|
+
# tasks in sub-threads.
|
|
128
|
+
@classmethod
|
|
129
|
+
def reset(cls, fake_exception_on_request=-1):
|
|
130
|
+
cls.mock_calls = []
|
|
131
|
+
cls.fake_return_values = cycle(iter(FAKE_LOAD_RESULTS))
|
|
132
|
+
cls.fake_exception_on_request = fake_exception_on_request
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def db_values_from_db_url(database_url):
|
|
136
|
+
engine = create_engine(database_url)
|
|
137
|
+
metadata = MetaData(engine)
|
|
138
|
+
metadata.reflect()
|
|
139
|
+
|
|
140
|
+
with engine.connect() as connection:
|
|
141
|
+
values = {
|
|
142
|
+
table_name: table_values(connection, table)
|
|
143
|
+
for table_name, table in metadata.tables.items()
|
|
144
|
+
if table_name[-6:] != "sf_ids"
|
|
145
|
+
}
|
|
146
|
+
return values
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@pytest.fixture
|
|
150
|
+
def mock_load_data(
|
|
151
|
+
request,
|
|
152
|
+
threads_instead_of_processes, # mock patches wouldn't be inherited by child processs
|
|
153
|
+
):
|
|
154
|
+
|
|
155
|
+
fake_load_data = FakeLoadData
|
|
156
|
+
with mock.patch(
|
|
157
|
+
"cumulusci.tasks.bulkdata.generate_and_load_data.LoadData", fake_load_data
|
|
158
|
+
), mock.patch(
|
|
159
|
+
"cumulusci.tasks.bulkdata.snowfakery_utils.queue_manager.LoadData",
|
|
160
|
+
fake_load_data,
|
|
161
|
+
):
|
|
162
|
+
fake_load_data.reset()
|
|
163
|
+
|
|
164
|
+
yield fake_load_data
|
|
165
|
+
fake_load_data.reset()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.fixture
|
|
169
|
+
def threads_instead_of_processes(request):
|
|
170
|
+
with mock.patch(
|
|
171
|
+
"cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
|
|
172
|
+
wraps=Thread,
|
|
173
|
+
) as t:
|
|
174
|
+
yield t
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@pytest.fixture
|
|
178
|
+
def fake_processes_and_threads(request):
|
|
179
|
+
class FakeProcessManager:
|
|
180
|
+
def __init__(self):
|
|
181
|
+
self.processes = []
|
|
182
|
+
|
|
183
|
+
def __call__(self, target, args, daemon):
|
|
184
|
+
res = self.process_handler(target, args, daemon, index=len(self.processes))
|
|
185
|
+
self.processes.append(res)
|
|
186
|
+
return res
|
|
187
|
+
|
|
188
|
+
process_manager = FakeProcessManager()
|
|
189
|
+
|
|
190
|
+
with mock.patch(
|
|
191
|
+
"cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Thread",
|
|
192
|
+
process_manager,
|
|
193
|
+
), mock.patch(
|
|
194
|
+
"cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
|
|
195
|
+
process_manager,
|
|
196
|
+
):
|
|
197
|
+
yield process_manager
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@pytest.fixture
|
|
201
|
+
def snowfakery(request, create_task):
|
|
202
|
+
def snowfakery(**kwargs):
|
|
203
|
+
return create_task(Snowfakery, kwargs)
|
|
204
|
+
|
|
205
|
+
return snowfakery
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@contextmanager
|
|
209
|
+
def temporary_file_path(filename):
|
|
210
|
+
with TemporaryDirectory() as tmpdirname:
|
|
211
|
+
path = Path(tmpdirname) / filename
|
|
212
|
+
yield path
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class SnowfakeryTaskResults(T.NamedTuple):
|
|
216
|
+
"""Results from a Snowfakery data generation process"""
|
|
217
|
+
|
|
218
|
+
task: Snowfakery # The task, so we can inspect its return_values
|
|
219
|
+
working_dir: Path # The working directory, to look at mapping files, DB files, etc.
|
|
220
|
+
values_loaded: T.List[T.Dict]
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.fixture()
|
|
224
|
+
def run_snowfakery_and_inspect_mapping(
|
|
225
|
+
run_snowfakery_and_yield_results,
|
|
226
|
+
):
|
|
227
|
+
"""Run Snowfakery with some defaulted or overriden options.
|
|
228
|
+
Yield a mapping file for inspection that it was the right file.
|
|
229
|
+
|
|
230
|
+
Defaults are same as run_snowfakery_and_yield_results.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def _run_snowfakery_and_inspect_mapping(**options):
|
|
234
|
+
with run_snowfakery_and_yield_results(**options) as results:
|
|
235
|
+
return get_mapping_from_snowfakery_task_results(results)
|
|
236
|
+
|
|
237
|
+
return _run_snowfakery_and_inspect_mapping
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def get_mapping_from_snowfakery_task_results(results: SnowfakeryTaskResults):
|
|
241
|
+
"""Find the shared mapping file and return it."""
|
|
242
|
+
template_dir = SnowfakeryWorkingDirectory(results.working_dir / "template_1/")
|
|
243
|
+
temp_mapping = template_dir.mapping_file
|
|
244
|
+
with open(temp_mapping) as f:
|
|
245
|
+
mapping = yaml.safe_load(f)
|
|
246
|
+
|
|
247
|
+
other_mapping = Path(
|
|
248
|
+
str(temp_mapping).replace("template_1", "data_load_outbox/1_1")
|
|
249
|
+
)
|
|
250
|
+
if other_mapping.exists():
|
|
251
|
+
# check that it's truly shared
|
|
252
|
+
assert temp_mapping.read_text() == other_mapping.read_text()
|
|
253
|
+
return mapping
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def get_record_counts_from_snowfakery_results(
|
|
257
|
+
results: SnowfakeryTaskResults,
|
|
258
|
+
) -> Counter:
|
|
259
|
+
"""Collate the record counts from Snowfakery outbox directories.
|
|
260
|
+
Note that records created by the initial, just_once seeding flow are not
|
|
261
|
+
counted because they are deleted. If you need every single result, you
|
|
262
|
+
should probably use return_values instead. (but you may need to implement it)"""
|
|
263
|
+
|
|
264
|
+
rollups = Counter()
|
|
265
|
+
# when there is more than one channel, the directory structure is deeper
|
|
266
|
+
channeled_outboxes = tuple(results.working_dir.glob("*/data_load_outbox/*"))
|
|
267
|
+
regular_outboxes = tuple(results.working_dir.glob("data_load_outbox/*"))
|
|
268
|
+
|
|
269
|
+
assert bool(regular_outboxes) ^ bool(
|
|
270
|
+
channeled_outboxes
|
|
271
|
+
), f"One of regular_outboxes or channeled_outboxes should be available: {channeled_outboxes}, {regular_outboxes}"
|
|
272
|
+
outboxes = tuple(channeled_outboxes) + tuple(regular_outboxes)
|
|
273
|
+
for subdir in outboxes:
|
|
274
|
+
record_counts = SnowfakeryWorkingDirectory(subdir).get_record_counts()
|
|
275
|
+
rollups.update(record_counts)
|
|
276
|
+
|
|
277
|
+
return rollups
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@pytest.fixture()
|
|
281
|
+
def run_snowfakery_and_yield_results(snowfakery, mock_load_data):
|
|
282
|
+
@contextmanager
|
|
283
|
+
def _run_snowfakery_and_inspect_mapping_and_example_records(**options):
|
|
284
|
+
with TemporaryDirectory() as workingdir:
|
|
285
|
+
workingdir = Path(workingdir) / "tempdir"
|
|
286
|
+
task = snowfakery(
|
|
287
|
+
working_directory=workingdir,
|
|
288
|
+
**options,
|
|
289
|
+
)
|
|
290
|
+
task()
|
|
291
|
+
values_loaded = [
|
|
292
|
+
mock_call.values_loaded for mock_call in mock_load_data.mock_calls
|
|
293
|
+
]
|
|
294
|
+
yield SnowfakeryTaskResults(task, workingdir, values_loaded)
|
|
295
|
+
|
|
296
|
+
return _run_snowfakery_and_inspect_mapping_and_example_records
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class TestSnowfakery:
|
|
300
|
+
def test_no_options(self):
|
|
301
|
+
with pytest.raises(exc.TaskOptionsError, match="recipe"):
|
|
302
|
+
_make_task(Snowfakery, {})
|
|
303
|
+
|
|
304
|
+
@mock.patch(
|
|
305
|
+
"cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
|
|
306
|
+
)
|
|
307
|
+
def test_simple_snowfakery(self, Process, mock_load_data, create_task):
|
|
308
|
+
task = create_task(
|
|
309
|
+
Snowfakery,
|
|
310
|
+
{
|
|
311
|
+
"recipe": sample_yaml,
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
task()
|
|
315
|
+
# should not be called for a simple one-rep load
|
|
316
|
+
assert not Process.mock_calls
|
|
317
|
+
|
|
318
|
+
@mock.patch(
|
|
319
|
+
"cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
|
|
320
|
+
)
|
|
321
|
+
@pytest.mark.vcr()
|
|
322
|
+
def test_snowfakery_query_salesforce(self, Process, mock_load_data, create_task):
|
|
323
|
+
task = create_task(
|
|
324
|
+
Snowfakery,
|
|
325
|
+
{
|
|
326
|
+
"recipe": query_yaml,
|
|
327
|
+
},
|
|
328
|
+
)
|
|
329
|
+
task()
|
|
330
|
+
assert mock_load_data.mock_calls
|
|
331
|
+
# should not be called for a simple one-rep load
|
|
332
|
+
assert not Process.mock_calls
|
|
333
|
+
|
|
334
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
|
|
335
|
+
def test_small(
|
|
336
|
+
self, mock_load_data, threads_instead_of_processes, create_task_fixture
|
|
337
|
+
):
|
|
338
|
+
task = create_task_fixture(
|
|
339
|
+
Snowfakery,
|
|
340
|
+
{
|
|
341
|
+
"recipe": sample_yaml,
|
|
342
|
+
"run_until_recipe_repeated": "7",
|
|
343
|
+
"drop_missing_schema": True,
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
task()
|
|
347
|
+
# Batch size was 3, so 7 records takes
|
|
348
|
+
# one initial batch plus two parallel batches
|
|
349
|
+
assert len(mock_load_data.mock_calls) == 3, mock_load_data.mock_calls
|
|
350
|
+
# One should be in a sub-process/thread
|
|
351
|
+
assert len(threads_instead_of_processes.mock_calls) == 2
|
|
352
|
+
for call in mock_load_data.mock_calls:
|
|
353
|
+
assert call.task_config.config["options"]["drop_missing_schema"] is True
|
|
354
|
+
|
|
355
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
|
|
356
|
+
def test_multi_part(
|
|
357
|
+
self, threads_instead_of_processes, mock_load_data, create_task_fixture
|
|
358
|
+
):
|
|
359
|
+
task = create_task_fixture(
|
|
360
|
+
Snowfakery,
|
|
361
|
+
{"recipe": sample_yaml, "run_until_recipe_repeated": 15},
|
|
362
|
+
)
|
|
363
|
+
task()
|
|
364
|
+
assert (
|
|
365
|
+
len(mock_load_data.mock_calls) > 3
|
|
366
|
+
) # depends on the details of the tuning
|
|
367
|
+
assert (
|
|
368
|
+
len(threads_instead_of_processes.mock_calls)
|
|
369
|
+
== len(mock_load_data.mock_calls) - 1
|
|
370
|
+
)
|
|
371
|
+
for call in mock_load_data.mock_calls:
|
|
372
|
+
assert call.task_config.config["options"]["drop_missing_schema"] is False
|
|
373
|
+
|
|
374
|
+
@mock.patch(
|
|
375
|
+
"cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
|
|
376
|
+
)
|
|
377
|
+
def test_run_until_loaded(
|
|
378
|
+
self, create_subprocess, mock_load_data, create_task_fixture
|
|
379
|
+
):
|
|
380
|
+
task = create_task_fixture(
|
|
381
|
+
Snowfakery,
|
|
382
|
+
{"recipe": sample_yaml, "run_until_records_loaded": "Account:1"},
|
|
383
|
+
)
|
|
384
|
+
task()
|
|
385
|
+
assert mock_load_data.mock_calls
|
|
386
|
+
# should not be called for a simple one-rep load
|
|
387
|
+
assert not create_subprocess.mock_calls
|
|
388
|
+
|
|
389
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
|
|
390
|
+
def test_run_until_loaded_2_parts(
|
|
391
|
+
self, threads_instead_of_processes, mock_load_data, create_task_fixture
|
|
392
|
+
):
|
|
393
|
+
task = create_task_fixture(
|
|
394
|
+
Snowfakery,
|
|
395
|
+
{"recipe": sample_yaml, "run_until_records_loaded": "Account:6"},
|
|
396
|
+
)
|
|
397
|
+
task()
|
|
398
|
+
assert len(mock_load_data.mock_calls) == 2
|
|
399
|
+
assert len(threads_instead_of_processes.mock_calls) == 1
|
|
400
|
+
|
|
401
|
+
# There was previously a failed attempt at testing the connected app here.
|
|
402
|
+
# Could try again after Snowfakery 2.0 launch.
|
|
403
|
+
# https://github.com/SFDO-Tooling/CumulusCI/blob/c7e0d7552394b3ac268cb373ffb24b72b5c059f3/cumulusci/tasks/bulkdata/tests/test_snowfakery.py#L165-L197https://github.com/SFDO-Tooling/CumulusCI/blob/c7e0d7552394b3ac268cb373ffb24b72b5c059f3/cumulusci/tasks/bulkdata/tests/test_snowfakery.py#L165-L197
|
|
404
|
+
|
|
405
|
+
@pytest.mark.vcr()
|
|
406
|
+
def test_run_until_records_in_org__none_needed(
|
|
407
|
+
self, threads_instead_of_processes, mock_load_data, create_task, ensure_accounts
|
|
408
|
+
):
|
|
409
|
+
with ensure_accounts(6):
|
|
410
|
+
task = create_task(
|
|
411
|
+
Snowfakery,
|
|
412
|
+
{"recipe": sample_yaml, "run_until_records_in_org": "Account:6"},
|
|
413
|
+
)
|
|
414
|
+
task()
|
|
415
|
+
assert len(mock_load_data.mock_calls) == 0, mock_load_data.mock_calls
|
|
416
|
+
assert (
|
|
417
|
+
len(threads_instead_of_processes.mock_calls) == 0
|
|
418
|
+
), threads_instead_of_processes.mock_calls
|
|
419
|
+
|
|
420
|
+
@pytest.mark.vcr()
|
|
421
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 5)
|
|
422
|
+
def test_run_until_records_in_org__one_needed(
|
|
423
|
+
self,
|
|
424
|
+
sf,
|
|
425
|
+
threads_instead_of_processes,
|
|
426
|
+
mock_load_data,
|
|
427
|
+
create_task,
|
|
428
|
+
ensure_accounts,
|
|
429
|
+
):
|
|
430
|
+
with ensure_accounts(10):
|
|
431
|
+
# org reports 10 records in org
|
|
432
|
+
# so we only need 6 more.
|
|
433
|
+
# That will be one "initial" batch of 1 plus one "parallel" batch of 5
|
|
434
|
+
task = create_task(
|
|
435
|
+
Snowfakery,
|
|
436
|
+
{"recipe": sample_yaml, "run_until_records_in_org": "Account:16"},
|
|
437
|
+
)
|
|
438
|
+
task.logger = mock.Mock()
|
|
439
|
+
task()
|
|
440
|
+
assert len(mock_load_data.mock_calls) == 2, mock_load_data.mock_calls
|
|
441
|
+
assert len(threads_instead_of_processes.mock_calls) == 1
|
|
442
|
+
|
|
443
|
+
@pytest.mark.vcr()
|
|
444
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
|
|
445
|
+
def test_run_until_records_in_org__multiple_needed(
|
|
446
|
+
self,
|
|
447
|
+
threads_instead_of_processes,
|
|
448
|
+
mock_load_data,
|
|
449
|
+
snowfakery,
|
|
450
|
+
ensure_accounts,
|
|
451
|
+
):
|
|
452
|
+
with ensure_accounts(10):
|
|
453
|
+
task = snowfakery(recipe=sample_yaml, run_until_records_in_org="Account:16")
|
|
454
|
+
task()
|
|
455
|
+
|
|
456
|
+
assert len(mock_load_data.mock_calls) == 2, mock_load_data.mock_calls
|
|
457
|
+
assert (
|
|
458
|
+
len(threads_instead_of_processes.mock_calls) == 1
|
|
459
|
+
), threads_instead_of_processes.mock_calls
|
|
460
|
+
|
|
461
|
+
def test_inaccessible_generator_yaml(self, snowfakery):
|
|
462
|
+
with pytest.raises(exc.TaskOptionsError, match="recipe"):
|
|
463
|
+
task = snowfakery(
|
|
464
|
+
recipe=sample_yaml / "junk",
|
|
465
|
+
)
|
|
466
|
+
task()
|
|
467
|
+
|
|
468
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.get_debug_mode", lambda: True)
|
|
469
|
+
@mock.patch("psutil.cpu_count", lambda logical: 11)
|
|
470
|
+
def test_snowfakery_debug_mode_and_cpu_count(self, snowfakery, mock_load_data):
|
|
471
|
+
task = snowfakery(recipe=sample_yaml, run_until_recipe_repeated="5")
|
|
472
|
+
with mock.patch.object(task, "logger") as logger:
|
|
473
|
+
task()
|
|
474
|
+
assert "Using 11 workers" in str(logger.mock_calls)
|
|
475
|
+
|
|
476
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
|
|
477
|
+
def test_record_count(self, snowfakery, mock_load_data):
|
|
478
|
+
task = snowfakery(recipe="datasets/recipe.yml", run_until_recipe_repeated="4")
|
|
479
|
+
with mock.patch.object(task, "logger") as logger, mock.patch.object(
|
|
480
|
+
task.project_config, "keychain", DummyKeychain()
|
|
481
|
+
) as keychain:
|
|
482
|
+
|
|
483
|
+
def get_org(username):
|
|
484
|
+
return DummyOrgConfig(
|
|
485
|
+
config={"keychain": keychain, "username": username}
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
489
|
+
task()
|
|
490
|
+
mock_calls_as_string = str(logger.mock_calls)
|
|
491
|
+
# note that load_data is mocked so these values are based on FAKE_LOAD_RESULTS
|
|
492
|
+
# See also: the TODO in FakeLoadData.__call__
|
|
493
|
+
assert "Account: 5 successes" in mock_calls_as_string, mock_calls_as_string[
|
|
494
|
+
-500:
|
|
495
|
+
]
|
|
496
|
+
assert "Contact: 5 successes" in mock_calls_as_string, mock_calls_as_string[
|
|
497
|
+
-500:
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
def test_run_until_wrong_format(self, snowfakery):
|
|
501
|
+
with pytest.raises(exc.TaskOptionsError, match="Ten"):
|
|
502
|
+
task = snowfakery(
|
|
503
|
+
recipe=sample_yaml, run_until_records_loaded="Account:Ten"
|
|
504
|
+
)
|
|
505
|
+
task()
|
|
506
|
+
|
|
507
|
+
def test_run_until_wrong_format__2(self, snowfakery):
|
|
508
|
+
with pytest.raises(exc.TaskOptionsError, match="Ten"):
|
|
509
|
+
task = snowfakery(
|
|
510
|
+
recipe=sample_yaml, run_until_records_loaded="Account_Ten"
|
|
511
|
+
)
|
|
512
|
+
task()
|
|
513
|
+
|
|
514
|
+
def test_run_reps_wrong_format(self, snowfakery):
|
|
515
|
+
with pytest.raises(exc.TaskOptionsError, match="Ten"):
|
|
516
|
+
task = snowfakery(recipe=sample_yaml, run_until_recipe_repeated="Ten")
|
|
517
|
+
task()
|
|
518
|
+
|
|
519
|
+
def test_run_until_conflicting_params(self, snowfakery):
|
|
520
|
+
with pytest.raises(exc.TaskOptionsError, match="only one of"):
|
|
521
|
+
task = snowfakery(
|
|
522
|
+
recipe=sample_yaml,
|
|
523
|
+
run_until_records_loaded="Account_Ten",
|
|
524
|
+
run_until_recipe_repeated="1",
|
|
525
|
+
)
|
|
526
|
+
task()
|
|
527
|
+
|
|
528
|
+
def test_working_directory(self, snowfakery, mock_load_data):
|
|
529
|
+
with TemporaryDirectory() as t:
|
|
530
|
+
working_directory = Path(t) / "junkdir"
|
|
531
|
+
task = snowfakery(
|
|
532
|
+
recipe=sample_yaml,
|
|
533
|
+
run_until_recipe_repeated="1",
|
|
534
|
+
working_directory=str(working_directory),
|
|
535
|
+
)
|
|
536
|
+
task()
|
|
537
|
+
assert (working_directory / "data_load_outbox").exists()
|
|
538
|
+
|
|
539
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 1)
|
|
540
|
+
def xxx__test_failures_in_subprocesses__last_batch(
|
|
541
|
+
self, snowfakery, mock_load_data, fake_processes_and_threads
|
|
542
|
+
):
|
|
543
|
+
class FakeProcess(DelaySpawner):
|
|
544
|
+
def __init__(self, target, args, daemon, index):
|
|
545
|
+
super().__init__(target, args, daemon)
|
|
546
|
+
self.counter = 0
|
|
547
|
+
self.task_class = args[0]["task_class"]
|
|
548
|
+
self.index = index
|
|
549
|
+
try:
|
|
550
|
+
self._finish()
|
|
551
|
+
except AssertionError:
|
|
552
|
+
pass
|
|
553
|
+
|
|
554
|
+
def is_alive(self):
|
|
555
|
+
print("Alive?", self.task_class, self.index, self.counter, self)
|
|
556
|
+
self.counter += 1
|
|
557
|
+
if self.counter > 3:
|
|
558
|
+
return False
|
|
559
|
+
return True
|
|
560
|
+
|
|
561
|
+
fake_processes_and_threads.process_handler = FakeProcess
|
|
562
|
+
|
|
563
|
+
class LoadDataSucceedsOnceThenFails:
|
|
564
|
+
count = 0
|
|
565
|
+
|
|
566
|
+
def __call__(self, *args, **kwargs):
|
|
567
|
+
self.count += 1
|
|
568
|
+
if self.count > 1:
|
|
569
|
+
raise AssertionError("XYZZY")
|
|
570
|
+
|
|
571
|
+
mock_load_data.side_effect = LoadDataSucceedsOnceThenFails()
|
|
572
|
+
|
|
573
|
+
task = snowfakery(
|
|
574
|
+
recipe=sample_yaml,
|
|
575
|
+
run_until_records_loaded="Account:10",
|
|
576
|
+
num_processes=3, # todo: test this is enforced
|
|
577
|
+
)
|
|
578
|
+
with mock.patch.object(task, "logger") as logger:
|
|
579
|
+
with pytest.raises(exc.BulkDataException):
|
|
580
|
+
task()
|
|
581
|
+
assert "XYZZY" in str(logger.mock_calls)
|
|
582
|
+
|
|
583
|
+
def test_running_totals_repr(self):
|
|
584
|
+
r = RunningTotals()
|
|
585
|
+
r.errors = 12
|
|
586
|
+
r.successes = 11
|
|
587
|
+
assert "11" in repr(r)
|
|
588
|
+
|
|
589
|
+
def test_generate_mapping_file__loadfile__inferred(
|
|
590
|
+
self, run_snowfakery_and_inspect_mapping
|
|
591
|
+
):
|
|
592
|
+
|
|
593
|
+
mapping = run_snowfakery_and_inspect_mapping(
|
|
594
|
+
recipe=simple_salesforce_yaml,
|
|
595
|
+
run_until_recipe_repeated=2,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
assert mapping["Insert Account"]["api"] == "bulk"
|
|
599
|
+
assert mapping["Insert Contact"].get("bulk_mode") is None
|
|
600
|
+
assert list(mapping.keys()) == ["Insert Account", "Insert Contact"]
|
|
601
|
+
|
|
602
|
+
def test_generate_mapping_file__loadfile__overridden(
|
|
603
|
+
self, run_snowfakery_and_inspect_mapping
|
|
604
|
+
):
|
|
605
|
+
loading_rules = str(simple_salesforce_yaml).replace(
|
|
606
|
+
".recipe.yml", "_2.load.yml"
|
|
607
|
+
)
|
|
608
|
+
mapping = run_snowfakery_and_inspect_mapping(
|
|
609
|
+
recipe=simple_salesforce_yaml,
|
|
610
|
+
loading_rules=str(loading_rules),
|
|
611
|
+
run_until_recipe_repeated=2,
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
assert mapping["Insert Account"].get("api") is None
|
|
615
|
+
assert mapping["Insert Contact"]["bulk_mode"].lower() == "parallel"
|
|
616
|
+
assert list(mapping.keys()) == ["Insert Contact", "Insert Account"]
|
|
617
|
+
|
|
618
|
+
def test_generate_mapping_file__loadfile_multiple_files(
|
|
619
|
+
self, run_snowfakery_and_inspect_mapping
|
|
620
|
+
):
|
|
621
|
+
loading_rules = (
|
|
622
|
+
str(simple_salesforce_yaml).replace(".recipe.yml", "_2.load.yml")
|
|
623
|
+
+ ","
|
|
624
|
+
+ str(simple_salesforce_yaml).replace(".recipe.yml", ".load.yml")
|
|
625
|
+
)
|
|
626
|
+
mapping = run_snowfakery_and_inspect_mapping(
|
|
627
|
+
recipe=simple_salesforce_yaml,
|
|
628
|
+
loading_rules=str(loading_rules),
|
|
629
|
+
run_until_recipe_repeated=2,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
assert mapping["Insert Account"]["api"] == "bulk"
|
|
633
|
+
assert mapping["Insert Contact"]["bulk_mode"].lower() == "parallel"
|
|
634
|
+
assert list(mapping.keys()) == ["Insert Contact", "Insert Account"]
|
|
635
|
+
|
|
636
|
+
@mock.patch(
|
|
637
|
+
"cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 1
|
|
638
|
+
) # force multi-step load
|
|
639
|
+
def test_options(
|
|
640
|
+
self,
|
|
641
|
+
mock_load_data,
|
|
642
|
+
run_snowfakery_and_yield_results,
|
|
643
|
+
):
|
|
644
|
+
options_yaml = str(sample_yaml).replace(
|
|
645
|
+
"gen_npsp_standard_objects.recipe.yml", "options.recipe.yml"
|
|
646
|
+
)
|
|
647
|
+
with run_snowfakery_and_yield_results(
|
|
648
|
+
recipe=options_yaml,
|
|
649
|
+
recipe_options="row_count:7,account_name:FakeAccountName",
|
|
650
|
+
run_until_recipe_repeated=2,
|
|
651
|
+
) as results:
|
|
652
|
+
record_counts = get_record_counts_from_snowfakery_results(results)
|
|
653
|
+
assert (
|
|
654
|
+
results.values_loaded[0]["Account"][0]["name"]
|
|
655
|
+
== "Account FakeAccountName"
|
|
656
|
+
)
|
|
657
|
+
assert record_counts["Account"] == 7, record_counts["Account"]
|
|
658
|
+
|
|
659
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
|
|
660
|
+
def test_multi_part_uniqueness(self, mock_load_data, create_task_fixture):
|
|
661
|
+
task = create_task_fixture(
|
|
662
|
+
Snowfakery,
|
|
663
|
+
{
|
|
664
|
+
"recipe": Path(__file__).parent / "snowfakery/unique_values.recipe.yml",
|
|
665
|
+
"run_until_recipe_repeated": 15,
|
|
666
|
+
},
|
|
667
|
+
)
|
|
668
|
+
task()
|
|
669
|
+
all_data_load_inputs = mock_load_data.mock_calls
|
|
670
|
+
all_rows = [
|
|
671
|
+
task_instance.values_loaded["blah"]
|
|
672
|
+
for task_instance in all_data_load_inputs
|
|
673
|
+
]
|
|
674
|
+
|
|
675
|
+
unique_values = [row.value for batchrows in all_rows for row in batchrows]
|
|
676
|
+
assert len(mock_load_data.mock_calls) == 6, len(mock_load_data.mock_calls)
|
|
677
|
+
assert len(unique_values) == 30, len(unique_values)
|
|
678
|
+
assert len(set(unique_values)) == 30, unique_values
|
|
679
|
+
|
|
680
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
|
|
681
|
+
def test_two_channels(self, mock_load_data, create_task):
|
|
682
|
+
task = create_task(
|
|
683
|
+
Snowfakery,
|
|
684
|
+
{
|
|
685
|
+
"recipe": Path(__file__).parent
|
|
686
|
+
/ "snowfakery/simple_snowfakery_channels.recipe.yml",
|
|
687
|
+
"run_until_recipe_repeated": 15,
|
|
688
|
+
"recipe_options": {"xyzzy": "Nothing happens", "some_number": 42},
|
|
689
|
+
},
|
|
690
|
+
)
|
|
691
|
+
with mock.patch.object(
|
|
692
|
+
task.project_config, "keychain", DummyKeychain()
|
|
693
|
+
) as keychain:
|
|
694
|
+
|
|
695
|
+
def get_org(username):
|
|
696
|
+
return DummyOrgConfig(
|
|
697
|
+
config={"keychain": keychain, "username": username}
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
701
|
+
task()
|
|
702
|
+
assert keychain.get_org.mock_calls
|
|
703
|
+
assert keychain.get_org.call_args_list
|
|
704
|
+
assert keychain.get_org.call_args_list == [
|
|
705
|
+
(("channeltest",),),
|
|
706
|
+
(("channeltest-b",),),
|
|
707
|
+
(("channeltest-c",),),
|
|
708
|
+
(("Account",),),
|
|
709
|
+
], keychain.get_org.call_args_list
|
|
710
|
+
|
|
711
|
+
all_data_load_inputs = mock_load_data.mock_calls
|
|
712
|
+
all_data_load_inputs = sorted(
|
|
713
|
+
all_data_load_inputs,
|
|
714
|
+
key=lambda task_instance: task_instance.org_config.username,
|
|
715
|
+
)
|
|
716
|
+
usernames_values = [
|
|
717
|
+
(task_instance.org_config.username, task_instance.values_loaded)
|
|
718
|
+
for task_instance in all_data_load_inputs
|
|
719
|
+
]
|
|
720
|
+
count_loads = Counter(username for username, _ in usernames_values)
|
|
721
|
+
assert count_loads.keys() == {
|
|
722
|
+
"channeltest",
|
|
723
|
+
"channeltest-b",
|
|
724
|
+
"channeltest-c",
|
|
725
|
+
"Account",
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
# depends on threading. :(
|
|
729
|
+
for value in count_loads.values():
|
|
730
|
+
assert 1 <= value <= 4, value
|
|
731
|
+
assert sum(count_loads.values()) == 8
|
|
732
|
+
|
|
733
|
+
first_row_values = next(
|
|
734
|
+
value["Account"]
|
|
735
|
+
for username, value in usernames_values
|
|
736
|
+
if username == "channeltest"
|
|
737
|
+
)
|
|
738
|
+
assert len(first_row_values) == 1, len(first_row_values)
|
|
739
|
+
for username, values in usernames_values:
|
|
740
|
+
accounts = values["Account"]
|
|
741
|
+
if values["Account"] != first_row_values:
|
|
742
|
+
assert len(accounts) == 2, (values, first_row_values)
|
|
743
|
+
for account in accounts:
|
|
744
|
+
assert int(account.some_number) == 42
|
|
745
|
+
assert username in account.name, (username, account.name)
|
|
746
|
+
|
|
747
|
+
assert sum(len(v["Account"]) for _, v in usernames_values) == 15, sum(
|
|
748
|
+
len(v) for _, v in usernames_values
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
def test_channels_cli_options_conflict(self, create_task):
|
|
752
|
+
task = create_task(
|
|
753
|
+
Snowfakery,
|
|
754
|
+
{
|
|
755
|
+
"recipe": Path(__file__).parent
|
|
756
|
+
/ "snowfakery/simple_snowfakery_channels.recipe.yml",
|
|
757
|
+
"run_until_recipe_repeated": 15,
|
|
758
|
+
"recipe_options": {"xyzzy": "Nothing happens", "some_number": 37},
|
|
759
|
+
},
|
|
760
|
+
)
|
|
761
|
+
with pytest.raises(exc.TaskOptionsError) as e, mock.patch.object(
|
|
762
|
+
task.project_config, "keychain", DummyKeychain()
|
|
763
|
+
) as keychain:
|
|
764
|
+
|
|
765
|
+
def get_org(username):
|
|
766
|
+
return DummyOrgConfig(
|
|
767
|
+
config={"keychain": keychain, "username": username}
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
771
|
+
task()
|
|
772
|
+
assert "conflict" in str(e.value)
|
|
773
|
+
assert "some_number" in str(e.value)
|
|
774
|
+
|
|
775
|
+
@mock.patch(
|
|
776
|
+
"cumulusci.tasks.bulkdata.snowfakery.get_debug_mode", lambda: True
|
|
777
|
+
) # for coverage
|
|
778
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
|
|
779
|
+
def test_explicit_channel_declarations(self, mock_load_data, create_task):
|
|
780
|
+
task = create_task(
|
|
781
|
+
Snowfakery,
|
|
782
|
+
{
|
|
783
|
+
"recipe": Path(__file__).parent
|
|
784
|
+
/ "snowfakery/simple_snowfakery.recipe.yml",
|
|
785
|
+
"run_until_recipe_repeated": 15,
|
|
786
|
+
"loading_rules": Path(__file__).parent
|
|
787
|
+
/ "snowfakery/simple_snowfakery_channels.load.yml",
|
|
788
|
+
},
|
|
789
|
+
)
|
|
790
|
+
with pytest.warns(UserWarning), mock.patch.object(
|
|
791
|
+
task.project_config, "keychain", DummyKeychain()
|
|
792
|
+
) as keychain:
|
|
793
|
+
|
|
794
|
+
def get_org(username):
|
|
795
|
+
return DummyOrgConfig(
|
|
796
|
+
config={"keychain": keychain, "username": username}
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
800
|
+
task()
|
|
801
|
+
assert keychain.get_org.mock_calls
|
|
802
|
+
assert keychain.get_org.call_args_list
|
|
803
|
+
assert keychain.get_org.call_args_list == [
|
|
804
|
+
(("channeltest",),),
|
|
805
|
+
(("channeltest-b",),),
|
|
806
|
+
(("channeltest-c",),),
|
|
807
|
+
(("Account",),),
|
|
808
|
+
], keychain.get_org.call_args_list
|
|
809
|
+
|
|
810
|
+
all_data_load_inputs = mock_load_data.mock_calls
|
|
811
|
+
all_data_load_inputs = sorted(
|
|
812
|
+
all_data_load_inputs,
|
|
813
|
+
key=lambda task_instance: task_instance.org_config.username,
|
|
814
|
+
)
|
|
815
|
+
usernames_values = [
|
|
816
|
+
(task_instance.org_config.username, task_instance.values_loaded)
|
|
817
|
+
for task_instance in all_data_load_inputs
|
|
818
|
+
]
|
|
819
|
+
count_loads = Counter(username for username, _ in usernames_values)
|
|
820
|
+
assert count_loads.keys() == {
|
|
821
|
+
"channeltest",
|
|
822
|
+
"channeltest-b",
|
|
823
|
+
"channeltest-c",
|
|
824
|
+
"Account",
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
|
|
828
|
+
def test_serial_mode(self, mock_load_data, create_task):
|
|
829
|
+
task = create_task(
|
|
830
|
+
Snowfakery,
|
|
831
|
+
{
|
|
832
|
+
"recipe": Path(__file__).parent
|
|
833
|
+
/ "snowfakery/simple_snowfakery.recipe.yml",
|
|
834
|
+
"run_until_recipe_repeated": 15,
|
|
835
|
+
"bulk_mode": "Serial",
|
|
836
|
+
},
|
|
837
|
+
)
|
|
838
|
+
with mock.patch.object(
|
|
839
|
+
task.project_config, "keychain", DummyKeychain()
|
|
840
|
+
) as keychain:
|
|
841
|
+
|
|
842
|
+
def get_org(username):
|
|
843
|
+
return DummyOrgConfig(
|
|
844
|
+
config={"keychain": keychain, "username": username}
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
848
|
+
task.logger = mock.Mock()
|
|
849
|
+
task()
|
|
850
|
+
for data_load_fake in mock_load_data.mock_calls:
|
|
851
|
+
assert data_load_fake.options["bulk_mode"] == "Serial"
|
|
852
|
+
pattern = r"Inprogress Loader Jobs: (\d+)"
|
|
853
|
+
loader_counts = re.findall(pattern, str(task.logger.mock_calls))
|
|
854
|
+
assert loader_counts, loader_counts
|
|
855
|
+
assert 0 <= all(int(count) <= 1 for count in loader_counts), loader_counts
|
|
856
|
+
|
|
857
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
|
|
858
|
+
def test_bulk_mode_error(self, create_task, mock_load_data):
|
|
859
|
+
with pytest.raises(exc.TaskOptionsError):
|
|
860
|
+
task = create_task(
|
|
861
|
+
Snowfakery,
|
|
862
|
+
{
|
|
863
|
+
"recipe": Path(__file__).parent
|
|
864
|
+
/ "snowfakery/simple_snowfakery.recipe.yml",
|
|
865
|
+
"bulk_mode": "XYZZY",
|
|
866
|
+
},
|
|
867
|
+
)
|
|
868
|
+
task()
|
|
869
|
+
|
|
870
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
|
|
871
|
+
def test_too_many_channel_declarations(self, mock_load_data, create_task):
|
|
872
|
+
task = create_task(
|
|
873
|
+
Snowfakery,
|
|
874
|
+
{
|
|
875
|
+
"recipe": Path(__file__).parent
|
|
876
|
+
/ "snowfakery/simple_snowfakery_channels.recipe.yml",
|
|
877
|
+
"run_until_recipe_repeated": 15,
|
|
878
|
+
"recipe_options": {"xyzzy": "Nothing happens", "some_number": 42},
|
|
879
|
+
"loading_rules": Path(__file__).parent
|
|
880
|
+
/ "snowfakery/simple_snowfakery_channels_2.load.yml",
|
|
881
|
+
},
|
|
882
|
+
)
|
|
883
|
+
with pytest.raises(exc.TaskOptionsError), mock.patch.object(
|
|
884
|
+
task.project_config, "keychain", DummyKeychain()
|
|
885
|
+
) as keychain:
|
|
886
|
+
|
|
887
|
+
def get_org(username):
|
|
888
|
+
return DummyOrgConfig(
|
|
889
|
+
config={"keychain": keychain, "username": username}
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
893
|
+
task()
|
|
894
|
+
|
|
895
|
+
@pytest.mark.skip() # TODO: make handling of errors more predictable and re-enable
|
|
896
|
+
@mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
|
|
897
|
+
def test_error_handling_in_channels(self, mock_load_data, create_task):
|
|
898
|
+
task = create_task(
|
|
899
|
+
Snowfakery,
|
|
900
|
+
{
|
|
901
|
+
"recipe": Path(__file__).parent
|
|
902
|
+
/ "snowfakery/simple_snowfakery.recipe.yml",
|
|
903
|
+
"run_until_recipe_repeated": 15,
|
|
904
|
+
"loading_rules": Path(__file__).parent
|
|
905
|
+
/ "snowfakery/simple_snowfakery_channels.load.yml",
|
|
906
|
+
},
|
|
907
|
+
)
|
|
908
|
+
with mock.patch.object(
|
|
909
|
+
task.project_config, "keychain", DummyKeychain()
|
|
910
|
+
) as keychain:
|
|
911
|
+
|
|
912
|
+
def get_org(username):
|
|
913
|
+
return DummyOrgConfig(
|
|
914
|
+
config={"keychain": keychain, "username": username}
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
keychain.get_org = mock.Mock(wraps=get_org)
|
|
918
|
+
with pytest.raises(exc.BulkDataException):
|
|
919
|
+
mock_load_data.reset(fake_exception_on_request=3)
|
|
920
|
+
task()
|
|
921
|
+
|
|
922
|
+
@pytest.mark.vcr()
|
|
923
|
+
@pytest.mark.skip()
|
|
924
|
+
def test_snowfakery_upsert(self, create_task, sf, run_code_without_recording):
|
|
925
|
+
task = create_task(
|
|
926
|
+
Snowfakery,
|
|
927
|
+
{
|
|
928
|
+
"recipe": Path(__file__).parent / "snowfakery/upsert.recipe.yml",
|
|
929
|
+
},
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
def assert_bluth_name(name):
|
|
933
|
+
data = sf.query(
|
|
934
|
+
"select FirstName from Contact where email='michael@bluth.com'"
|
|
935
|
+
)
|
|
936
|
+
assert data["records"][0]["FirstName"] == name
|
|
937
|
+
|
|
938
|
+
task()
|
|
939
|
+
run_code_without_recording(lambda: assert_bluth_name("Michael"))
|
|
940
|
+
|
|
941
|
+
task = create_task(
|
|
942
|
+
Snowfakery,
|
|
943
|
+
{
|
|
944
|
+
"recipe": Path(__file__).parent / "snowfakery/upsert_2.recipe.yml",
|
|
945
|
+
},
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
task()
|
|
949
|
+
run_code_without_recording(lambda: assert_bluth_name("Nichael"))
|
|
950
|
+
|
|
951
|
+
# def test_generate_mapping_file(self):
|
|
952
|
+
# with temporary_file_path("mapping.yml") as temp_mapping:
|
|
953
|
+
# with temp_sqlite_database_url() as database_url:
|
|
954
|
+
# task = _make_task(
|
|
955
|
+
# GenerateDataFromYaml,
|
|
956
|
+
# {
|
|
957
|
+
# "options": {
|
|
958
|
+
# "generator_yaml": sample_yaml,
|
|
959
|
+
# "database_url": database_url,
|
|
960
|
+
# "generate_mapping_file": temp_mapping,
|
|
961
|
+
# }
|
|
962
|
+
# },
|
|
963
|
+
# )
|
|
964
|
+
# task()
|
|
965
|
+
# mapping = yaml.safe_load(open(temp_mapping))
|
|
966
|
+
# assert mapping["Insert Account"]["fields"]
|
|
967
|
+
|
|
968
|
+
# def test_use_mapping_file(self):
|
|
969
|
+
# assert vanilla_mapping_file.exists()
|
|
970
|
+
# with temp_sqlite_database_url() as database_url:
|
|
971
|
+
# task = _make_task(
|
|
972
|
+
# GenerateDataFromYaml,
|
|
973
|
+
# {
|
|
974
|
+
# "options": {
|
|
975
|
+
# "generator_yaml": sample_yaml,
|
|
976
|
+
# "database_url": database_url,
|
|
977
|
+
# "mapping": vanilla_mapping_file,
|
|
978
|
+
# }
|
|
979
|
+
# },
|
|
980
|
+
# )
|
|
981
|
+
# task()
|
|
982
|
+
# self.assertRowsCreated(database_url)
|
|
983
|
+
|
|
984
|
+
# def test_num_records(self):
|
|
985
|
+
# with temp_sqlite_database_url() as database_url:
|
|
986
|
+
# task = _make_task(
|
|
987
|
+
# GenerateDataFromYaml,
|
|
988
|
+
# {
|
|
989
|
+
# "options": {
|
|
990
|
+
# "generator_yaml": simple_yaml,
|
|
991
|
+
# "database_url": database_url,
|
|
992
|
+
# }
|
|
993
|
+
# },
|
|
994
|
+
# )
|
|
995
|
+
# task()
|
|
996
|
+
# assert len(self.assertRowsCreated(database_url)) == 1, len(
|
|
997
|
+
# self.assertRowsCreated(database_url)
|
|
998
|
+
# )
|
|
999
|
+
|
|
1000
|
+
# @mock.patch(
|
|
1001
|
+
# "cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml.GenerateAndLoadDataFromYaml._dataload"
|
|
1002
|
+
# )
|
|
1003
|
+
# def test_simple_generate_and_load_with_numrecords(self, _dataload):
|
|
1004
|
+
# task = _make_task(s
|
|
1005
|
+
# GenerateAndLoadDataFromYaml,
|
|
1006
|
+
# {
|
|
1007
|
+
# "options": {
|
|
1008
|
+
# "generator_yaml": simple_yaml,
|
|
1009
|
+
# "num_records": 11,
|
|
1010
|
+
# "num_records_tablename": "Account",
|
|
1011
|
+
# }
|
|
1012
|
+
# },
|
|
1013
|
+
# )
|
|
1014
|
+
# task()
|
|
1015
|
+
# assert len(_dataload.mock_calls) == 1
|
|
1016
|
+
|
|
1017
|
+
# @mock.patch(
|
|
1018
|
+
# "cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml.GenerateAndLoadDataFromYaml._dataload"
|
|
1019
|
+
# )
|
|
1020
|
+
# def test_simple_generate_and_load(self, _dataload):
|
|
1021
|
+
# task = _make_task(
|
|
1022
|
+
# GenerateAndLoadDataFromYaml,
|
|
1023
|
+
# {
|
|
1024
|
+
# "options": {
|
|
1025
|
+
# "generator_yaml": simple_yaml,
|
|
1026
|
+
# "num_records": 11,
|
|
1027
|
+
# "num_records_tablename": "Account",
|
|
1028
|
+
# }
|
|
1029
|
+
# },
|
|
1030
|
+
# )
|
|
1031
|
+
# task()
|
|
1032
|
+
# assert len(_dataload.mock_calls) == 1
|
|
1033
|
+
|
|
1034
|
+
# @mock.patch("cumulusci.tasks.bulkdata.generate_from_yaml.generate_data")
|
|
1035
|
+
# def test_exception_handled_cleanly(self, generate_data):
|
|
1036
|
+
# generate_data.side_effect = AssertionError("Foo")
|
|
1037
|
+
# with pytest.raises(AssertionError) as e:
|
|
1038
|
+
# task = _make_task(
|
|
1039
|
+
# GenerateAndLoadDataFromYaml,
|
|
1040
|
+
# {
|
|
1041
|
+
# "options": {
|
|
1042
|
+
# "generator_yaml": simple_yaml,
|
|
1043
|
+
# "num_records": 11,
|
|
1044
|
+
# "num_records_tablename": "Account",
|
|
1045
|
+
# }
|
|
1046
|
+
# },
|
|
1047
|
+
# )
|
|
1048
|
+
# task()
|
|
1049
|
+
# assert "Foo" in str(e.value)
|
|
1050
|
+
# assert len(generate_data.mock_calls) == 1
|
|
1051
|
+
|
|
1052
|
+
# @mock.patch(
|
|
1053
|
+
# "cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml.GenerateAndLoadDataFromYaml._dataload"
|
|
1054
|
+
# )
|
|
1055
|
+
# def test_batching(self, _dataload):
|
|
1056
|
+
# with temp_sqlite_database_url() as database_url:
|
|
1057
|
+
# task = _make_task(
|
|
1058
|
+
# GenerateAndLoadDataFromYaml,
|
|
1059
|
+
# {
|
|
1060
|
+
# "options": {
|
|
1061
|
+
# "generator_yaml": simple_yaml,
|
|
1062
|
+
# "num_records": 14,
|
|
1063
|
+
# "batch_size": 6,
|
|
1064
|
+
# "database_url": database_url,
|
|
1065
|
+
# "num_records_tablename": "Account",
|
|
1066
|
+
# "data_generation_task": "cumulusci.tasks.bulkdata.generate_from_yaml.GenerateDataFromYaml",
|
|
1067
|
+
# "reset_oids": False,
|
|
1068
|
+
# }
|
|
1069
|
+
# },
|
|
1070
|
+
# )
|
|
1071
|
+
# task()
|
|
1072
|
+
# assert len(_dataload.mock_calls) == 3
|
|
1073
|
+
# task = None # clean up db?
|
|
1074
|
+
|
|
1075
|
+
# engine = create_engine(database_url)
|
|
1076
|
+
# connection = engine.connect()
|
|
1077
|
+
# records = list(connection.execute("select * from Account"))
|
|
1078
|
+
# connection.close()
|
|
1079
|
+
# assert len(records) == 14 % 6 # leftovers
|
|
1080
|
+
|
|
1081
|
+
# def test_mismatched_options(self):
|
|
1082
|
+
# with pytest.raises(exc.exc.TaskOptionsError) as e:
|
|
1083
|
+
# task = _make_task(
|
|
1084
|
+
# GenerateDataFromYaml,
|
|
1085
|
+
# {"options": {"generator_yaml": sample_yaml, "num_records": 10}},
|
|
1086
|
+
# )
|
|
1087
|
+
# task()
|
|
1088
|
+
# assert "without num_records_tablename" in str(e.value)
|
|
1089
|
+
|
|
1090
|
+
# def test_with_nonexistent_continuation_file(self):
|
|
1091
|
+
# with pytest.raises(exc.TaskOptionsError) as e:
|
|
1092
|
+
# with temp_sqlite_database_url() as database_url:
|
|
1093
|
+
# task = _make_task(
|
|
1094
|
+
# GenerateDataFromYaml,
|
|
1095
|
+
# {
|
|
1096
|
+
# "options": {
|
|
1097
|
+
# "generator_yaml": sample_yaml,
|
|
1098
|
+
# "database_url": database_url,
|
|
1099
|
+
# "mapping": vanilla_mapping_file,
|
|
1100
|
+
# "continuation_file": "/tmp/foobar/baz/jazz/continuation.yml",
|
|
1101
|
+
# }
|
|
1102
|
+
# },
|
|
1103
|
+
# )
|
|
1104
|
+
# task()
|
|
1105
|
+
# rows = self.assertRowsCreated(database_url)
|
|
1106
|
+
# assert dict(rows[0])["id"] == 6
|
|
1107
|
+
|
|
1108
|
+
# assert "jazz" in str(e.value)
|
|
1109
|
+
# assert "does not exist" in str(e.value)
|
|
1110
|
+
|
|
1111
|
+
# def test_generate_continuation_file(self):
|
|
1112
|
+
# with temporary_file_path("cont.yml") as temp_continuation_file:
|
|
1113
|
+
# with temp_sqlite_database_url() as database_url:
|
|
1114
|
+
# task = _make_task(
|
|
1115
|
+
# GenerateDataFromYaml,
|
|
1116
|
+
# {
|
|
1117
|
+
# "options": {
|
|
1118
|
+
# "generator_yaml": sample_yaml,
|
|
1119
|
+
# "database_url": database_url,
|
|
1120
|
+
# "generate_continuation_file": temp_continuation_file,
|
|
1121
|
+
# }
|
|
1122
|
+
# },
|
|
1123
|
+
# )
|
|
1124
|
+
# task()
|
|
1125
|
+
# continuation_file = yaml.safe_load(open(temp_continuation_file))
|
|
1126
|
+
# assert continuation_file # internals of this file are not important to CumulusCI
|
|
1127
|
+
|
|
1128
|
+
# def _run_snowfakery_and_inspect_mapping(self, **options):
|
|
1129
|
+
# with temporary_file_path("mapping.yml") as temp_mapping:
|
|
1130
|
+
# with temp_sqlite_database_url() as database_url:
|
|
1131
|
+
# task = _make_task(
|
|
1132
|
+
# GenerateDataFromYaml,
|
|
1133
|
+
# {
|
|
1134
|
+
# "options": {
|
|
1135
|
+
# "database_url": database_url,
|
|
1136
|
+
# "generate_mapping_file": temp_mapping,
|
|
1137
|
+
# **options,
|
|
1138
|
+
# }
|
|
1139
|
+
# },
|
|
1140
|
+
# )
|
|
1141
|
+
# task()
|
|
1142
|
+
# with open(temp_mapping) as f:
|
|
1143
|
+
# mapping = yaml.safe_load(f)
|
|
1144
|
+
# return mapping
|
|
1145
|
+
|
|
1146
|
+
# def test_generate_mapping_file__loadfile_missing(self):
|
|
1147
|
+
# loading_rules = str(simple_snowfakery_yaml).replace(
|
|
1148
|
+
# ".recipe.yml", "_3.load.yml"
|
|
1149
|
+
# )
|
|
1150
|
+
# with pytest.raises(FileNotFoundError):
|
|
1151
|
+
# self._run_snowfakery_and_inspect_mapping(
|
|
1152
|
+
# generator_yaml=simple_snowfakery_yaml, loading_rules=str(loading_rules)
|
|
1153
|
+
# )
|