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,1057 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from cumulusci.tasks.bulkdata.select_utils import (
|
|
4
|
+
OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
5
|
+
SelectOperationExecutor,
|
|
6
|
+
SelectStrategy,
|
|
7
|
+
add_limit_offset_to_user_filter,
|
|
8
|
+
annoy_post_process,
|
|
9
|
+
calculate_levenshtein_distance,
|
|
10
|
+
determine_field_types,
|
|
11
|
+
find_closest_record,
|
|
12
|
+
levenshtein_distance,
|
|
13
|
+
reorder_records,
|
|
14
|
+
split_and_filter_fields,
|
|
15
|
+
vectorize_records,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Check for pandas availability
|
|
19
|
+
try:
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
PANDAS_AVAILABLE = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
PANDAS_AVAILABLE = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_standard_generate_query_without_filter():
|
|
28
|
+
select_operator = SelectOperationExecutor(SelectStrategy.STANDARD)
|
|
29
|
+
sobject = "Contact" # Assuming no declaration for this object
|
|
30
|
+
limit = 3
|
|
31
|
+
offset = None
|
|
32
|
+
query, fields = select_operator.select_generate_query(
|
|
33
|
+
sobject=sobject, fields=[], user_filter="", limit=limit, offset=offset
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
assert f"LIMIT {limit}" in query
|
|
37
|
+
assert "OFFSET" not in query
|
|
38
|
+
assert fields == ["Id"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_standard_generate_query_with_user_filter():
|
|
42
|
+
select_operator = SelectOperationExecutor(SelectStrategy.STANDARD)
|
|
43
|
+
sobject = "Contact" # Assuming no declaration for this object
|
|
44
|
+
limit = 3
|
|
45
|
+
offset = None
|
|
46
|
+
user_filter = "WHERE Name IN ('Sample Contact')"
|
|
47
|
+
query, fields = select_operator.select_generate_query(
|
|
48
|
+
sobject=sobject, fields=[], user_filter=user_filter, limit=limit, offset=offset
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
assert "WHERE" in query
|
|
52
|
+
assert "Sample Contact" in query
|
|
53
|
+
assert "LIMIT" in query
|
|
54
|
+
assert "OFFSET" not in query
|
|
55
|
+
assert fields == ["Id"]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_random_generate_query():
|
|
59
|
+
select_operator = SelectOperationExecutor(SelectStrategy.RANDOM)
|
|
60
|
+
sobject = "Contact" # Assuming no declaration for this object
|
|
61
|
+
limit = 3
|
|
62
|
+
offset = None
|
|
63
|
+
query, fields = select_operator.select_generate_query(
|
|
64
|
+
sobject=sobject, fields=[], user_filter="", limit=limit, offset=offset
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
assert f"LIMIT {limit}" in query
|
|
68
|
+
assert "OFFSET" not in query
|
|
69
|
+
assert fields == ["Id"]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Test Cases for standard_post_process
|
|
73
|
+
def test_standard_post_process_with_records():
|
|
74
|
+
select_operator = SelectOperationExecutor(SelectStrategy.STANDARD)
|
|
75
|
+
records = [["001"], ["002"], ["003"]]
|
|
76
|
+
num_records = 3
|
|
77
|
+
sobject = "Contact"
|
|
78
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
79
|
+
load_records=None,
|
|
80
|
+
query_records=records,
|
|
81
|
+
num_records=num_records,
|
|
82
|
+
sobject=sobject,
|
|
83
|
+
weights=[],
|
|
84
|
+
fields=[],
|
|
85
|
+
threshold=None,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
assert error_message is None
|
|
89
|
+
assert len(selected_records) == num_records
|
|
90
|
+
assert all(record["success"] for record in selected_records)
|
|
91
|
+
assert all(record["created"] is False for record in selected_records)
|
|
92
|
+
assert all(record["id"] in ["001", "002", "003"] for record in selected_records)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_standard_post_process_with_fewer_records():
|
|
96
|
+
select_operator = SelectOperationExecutor(SelectStrategy.STANDARD)
|
|
97
|
+
records = [["001"]]
|
|
98
|
+
num_records = 3
|
|
99
|
+
sobject = "Opportunity"
|
|
100
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
101
|
+
load_records=None,
|
|
102
|
+
query_records=records,
|
|
103
|
+
num_records=num_records,
|
|
104
|
+
sobject=sobject,
|
|
105
|
+
weights=[],
|
|
106
|
+
fields=[],
|
|
107
|
+
threshold=None,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
assert error_message is None
|
|
111
|
+
assert len(selected_records) == num_records
|
|
112
|
+
assert all(record["success"] for record in selected_records)
|
|
113
|
+
assert all(record["created"] is False for record in selected_records)
|
|
114
|
+
# Check if records are repeated to match num_records
|
|
115
|
+
assert selected_records.count({"id": "001", "success": True, "created": False}) == 3
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_standard_post_process_with_no_records():
|
|
119
|
+
select_operator = SelectOperationExecutor(SelectStrategy.STANDARD)
|
|
120
|
+
records = []
|
|
121
|
+
num_records = 2
|
|
122
|
+
sobject = "Lead"
|
|
123
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
124
|
+
load_records=None,
|
|
125
|
+
query_records=records,
|
|
126
|
+
num_records=num_records,
|
|
127
|
+
sobject=sobject,
|
|
128
|
+
weights=[],
|
|
129
|
+
fields=[],
|
|
130
|
+
threshold=None,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert selected_records == []
|
|
134
|
+
assert error_message == f"No records found for {sobject} in the target org."
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Test cases for Random Post Process
|
|
138
|
+
def test_random_post_process_with_records():
|
|
139
|
+
select_operator = SelectOperationExecutor(SelectStrategy.RANDOM)
|
|
140
|
+
records = [["001"], ["002"], ["003"]]
|
|
141
|
+
num_records = 3
|
|
142
|
+
sobject = "Contact"
|
|
143
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
144
|
+
load_records=None,
|
|
145
|
+
query_records=records,
|
|
146
|
+
num_records=num_records,
|
|
147
|
+
sobject=sobject,
|
|
148
|
+
weights=[],
|
|
149
|
+
fields=[],
|
|
150
|
+
threshold=None,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
assert error_message is None
|
|
154
|
+
assert len(selected_records) == num_records
|
|
155
|
+
assert all(record["success"] for record in selected_records)
|
|
156
|
+
assert all(record["created"] is False for record in selected_records)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_random_post_process_with_no_records():
|
|
160
|
+
select_operator = SelectOperationExecutor(SelectStrategy.RANDOM)
|
|
161
|
+
records = []
|
|
162
|
+
num_records = 2
|
|
163
|
+
sobject = "Lead"
|
|
164
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
165
|
+
load_records=None,
|
|
166
|
+
query_records=records,
|
|
167
|
+
num_records=num_records,
|
|
168
|
+
sobject=sobject,
|
|
169
|
+
weights=[],
|
|
170
|
+
fields=[],
|
|
171
|
+
threshold=None,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
assert selected_records == []
|
|
175
|
+
assert error_message == f"No records found for {sobject} in the target org."
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_similarity_generate_query_no_nesting():
|
|
179
|
+
select_operator = SelectOperationExecutor(SelectStrategy.SIMILARITY)
|
|
180
|
+
sobject = "Contact" # Assuming no declaration for this object
|
|
181
|
+
limit = 3
|
|
182
|
+
offset = None
|
|
183
|
+
query, fields = select_operator.select_generate_query(
|
|
184
|
+
sobject, ["Name"], [], limit, offset
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert fields == ["Id", "Name"]
|
|
188
|
+
assert f"LIMIT {limit}" in query
|
|
189
|
+
assert "OFFSET" not in query
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_similarity_generate_query_with_nested_fields():
|
|
193
|
+
select_operator = SelectOperationExecutor(SelectStrategy.SIMILARITY)
|
|
194
|
+
sobject = "Event" # Assuming no declaration for this object
|
|
195
|
+
limit = 3
|
|
196
|
+
offset = None
|
|
197
|
+
fields = [
|
|
198
|
+
"Subject",
|
|
199
|
+
"Who.Contact.Name",
|
|
200
|
+
"Who.Contact.Email",
|
|
201
|
+
"Who.Lead.Name",
|
|
202
|
+
"Who.Lead.Company",
|
|
203
|
+
]
|
|
204
|
+
query, query_fields = select_operator.select_generate_query(
|
|
205
|
+
sobject, fields, [], limit, offset
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
assert "WHERE" not in query # No WHERE clause should be present
|
|
209
|
+
assert query_fields == [
|
|
210
|
+
"Id",
|
|
211
|
+
"Subject",
|
|
212
|
+
"Who.Contact.Name",
|
|
213
|
+
"Who.Contact.Email",
|
|
214
|
+
"Who.Lead.Name",
|
|
215
|
+
"Who.Lead.Company",
|
|
216
|
+
]
|
|
217
|
+
assert f"LIMIT {limit}" in query
|
|
218
|
+
assert "TYPEOF Who" in query
|
|
219
|
+
assert "WHEN Contact" in query
|
|
220
|
+
assert "WHEN Lead" in query
|
|
221
|
+
assert "OFFSET" not in query
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_random_generate_query_with_user_filter():
|
|
225
|
+
select_operator = SelectOperationExecutor(SelectStrategy.SIMILARITY)
|
|
226
|
+
sobject = "Contact" # Assuming no declaration for this object
|
|
227
|
+
limit = 3
|
|
228
|
+
offset = None
|
|
229
|
+
user_filter = "WHERE Name IN ('Sample Contact')"
|
|
230
|
+
query, fields = select_operator.select_generate_query(
|
|
231
|
+
sobject=sobject,
|
|
232
|
+
fields=["Name"],
|
|
233
|
+
user_filter=user_filter,
|
|
234
|
+
limit=limit,
|
|
235
|
+
offset=offset,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
assert "WHERE" in query
|
|
239
|
+
assert "Sample Contact" in query
|
|
240
|
+
assert "LIMIT" in query
|
|
241
|
+
assert "OFFSET" not in query
|
|
242
|
+
assert fields == ["Id", "Name"]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_levenshtein_distance():
|
|
246
|
+
assert levenshtein_distance("kitten", "kitten") == 0 # Identical strings
|
|
247
|
+
assert levenshtein_distance("kitten", "sitten") == 1 # One substitution
|
|
248
|
+
assert levenshtein_distance("kitten", "kitte") == 1 # One deletion
|
|
249
|
+
assert levenshtein_distance("kitten", "sittin") == 2 # Two substitutions
|
|
250
|
+
assert levenshtein_distance("kitten", "dog") == 6 # Completely different strings
|
|
251
|
+
assert levenshtein_distance("kitten", "") == 6 # One string is empty
|
|
252
|
+
assert levenshtein_distance("", "") == 0 # Both strings are empty
|
|
253
|
+
assert levenshtein_distance("Kitten", "kitten") == 1 # Case sensitivity
|
|
254
|
+
assert levenshtein_distance("kit ten", "kitten") == 1 # Strings with spaces
|
|
255
|
+
assert (
|
|
256
|
+
levenshtein_distance("levenshtein", "meilenstein") == 4
|
|
257
|
+
) # Longer strings with multiple differences
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_find_closest_record_different_weights():
|
|
261
|
+
load_record = ["hello", "world"]
|
|
262
|
+
query_records = [
|
|
263
|
+
["record1", "hello", "word"], # Levenshtein distance = 1
|
|
264
|
+
["record2", "hullo", "word"], # Levenshtein distance = 1
|
|
265
|
+
["record3", "hello", "word"], # Levenshtein distance = 1
|
|
266
|
+
]
|
|
267
|
+
weights = [2.0, 0.5]
|
|
268
|
+
|
|
269
|
+
# With different weights, the first field will have more impact
|
|
270
|
+
closest_record, _ = find_closest_record(load_record, query_records, weights)
|
|
271
|
+
assert closest_record == [
|
|
272
|
+
"record1",
|
|
273
|
+
"hello",
|
|
274
|
+
"word",
|
|
275
|
+
], "The closest record should be 'record1'."
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_find_closest_record_basic():
|
|
279
|
+
load_record = ["hello", "world"]
|
|
280
|
+
query_records = [
|
|
281
|
+
["record1", "hello", "word"], # Levenshtein distance = 1
|
|
282
|
+
["record2", "hullo", "word"], # Levenshtein distance = 1
|
|
283
|
+
["record3", "hello", "word"], # Levenshtein distance = 1
|
|
284
|
+
]
|
|
285
|
+
weights = [1.0, 1.0]
|
|
286
|
+
|
|
287
|
+
closest_record, _ = find_closest_record(load_record, query_records, weights)
|
|
288
|
+
assert closest_record == [
|
|
289
|
+
"record1",
|
|
290
|
+
"hello",
|
|
291
|
+
"word",
|
|
292
|
+
], "The closest record should be 'record1'."
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_find_closest_record_multiple_matches():
|
|
296
|
+
load_record = ["cat", "dog"]
|
|
297
|
+
query_records = [
|
|
298
|
+
["record1", "bat", "dog"], # Levenshtein distance = 1
|
|
299
|
+
["record2", "cat", "dog"], # Levenshtein distance = 0
|
|
300
|
+
["record3", "dog", "cat"], # Levenshtein distance = 3
|
|
301
|
+
]
|
|
302
|
+
weights = [1.0, 1.0]
|
|
303
|
+
|
|
304
|
+
closest_record, _ = find_closest_record(load_record, query_records, weights)
|
|
305
|
+
assert closest_record == [
|
|
306
|
+
"record2",
|
|
307
|
+
"cat",
|
|
308
|
+
"dog",
|
|
309
|
+
], "The closest record should be 'record2'."
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def test_similarity_post_process_with_records():
|
|
313
|
+
select_operator = SelectOperationExecutor(SelectStrategy.SIMILARITY)
|
|
314
|
+
num_records = 1
|
|
315
|
+
sobject = "Contact"
|
|
316
|
+
load_records = [["Tom Cruise", "62", "Actor"]]
|
|
317
|
+
query_records = [
|
|
318
|
+
["001", "Bob Hanks", "62", "Actor"],
|
|
319
|
+
["002", "Tom Cruise", "63", "Actor"], # Slight difference
|
|
320
|
+
["003", "Jennifer Aniston", "30", "Actress"],
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
weights = [1.0, 1.0, 1.0] # Adjust weights to match your data structure
|
|
324
|
+
|
|
325
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
326
|
+
load_records=load_records,
|
|
327
|
+
query_records=query_records,
|
|
328
|
+
num_records=num_records,
|
|
329
|
+
sobject=sobject,
|
|
330
|
+
weights=weights,
|
|
331
|
+
fields=["Name", "Age", "Occupation"],
|
|
332
|
+
threshold=None,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
assert error_message is None
|
|
336
|
+
assert len(selected_records) == num_records
|
|
337
|
+
assert all(record["success"] for record in selected_records)
|
|
338
|
+
assert all(record["created"] is False for record in selected_records)
|
|
339
|
+
x = [record["id"] for record in selected_records]
|
|
340
|
+
print(x)
|
|
341
|
+
assert all(record["id"] in ["002"] for record in selected_records)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def test_similarity_post_process_with_no_records():
|
|
345
|
+
select_operator = SelectOperationExecutor(SelectStrategy.SIMILARITY)
|
|
346
|
+
records = []
|
|
347
|
+
num_records = 2
|
|
348
|
+
sobject = "Lead"
|
|
349
|
+
selected_records, _, error_message = select_operator.select_post_process(
|
|
350
|
+
load_records=None,
|
|
351
|
+
query_records=records,
|
|
352
|
+
num_records=num_records,
|
|
353
|
+
sobject=sobject,
|
|
354
|
+
weights=[1, 1, 1],
|
|
355
|
+
fields=[],
|
|
356
|
+
threshold=None,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
assert selected_records == []
|
|
360
|
+
assert error_message == f"No records found for {sobject} in the target org."
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def test_similarity_post_process_with_no_records__zero_threshold():
|
|
364
|
+
select_operator = SelectOperationExecutor(SelectStrategy.SIMILARITY)
|
|
365
|
+
load_records = [["Aditya", "Salesforce"], ["Jawad", "Salesforce"]]
|
|
366
|
+
query_records = []
|
|
367
|
+
num_records = 2
|
|
368
|
+
sobject = "Lead"
|
|
369
|
+
(
|
|
370
|
+
selected_records,
|
|
371
|
+
insert_records,
|
|
372
|
+
error_message,
|
|
373
|
+
) = select_operator.select_post_process(
|
|
374
|
+
load_records=load_records,
|
|
375
|
+
query_records=query_records,
|
|
376
|
+
num_records=num_records,
|
|
377
|
+
sobject=sobject,
|
|
378
|
+
weights=[1, 1, 1],
|
|
379
|
+
fields=["LastName", "Company"],
|
|
380
|
+
threshold=0,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Assert that it inserts everything
|
|
384
|
+
assert selected_records == [None, None]
|
|
385
|
+
assert insert_records[0] == ["Aditya", "Salesforce"]
|
|
386
|
+
assert insert_records[1] == ["Jawad", "Salesforce"]
|
|
387
|
+
assert error_message is None
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_calculate_levenshtein_distance_basic():
|
|
391
|
+
record1 = ["hello", "world"]
|
|
392
|
+
record2 = ["hullo", "word"]
|
|
393
|
+
weights = [1.0, 1.0]
|
|
394
|
+
|
|
395
|
+
# Expected distance based on simple Levenshtein distances
|
|
396
|
+
# Levenshtein("hello", "hullo") = 1, Levenshtein("world", "word") = 1
|
|
397
|
+
expected_distance = (1 / 5 * 1.0 + 1 / 5 * 1.0) / 2 # Averaged over two fields
|
|
398
|
+
|
|
399
|
+
result = calculate_levenshtein_distance(record1, record2, weights)
|
|
400
|
+
assert result == pytest.approx(
|
|
401
|
+
expected_distance
|
|
402
|
+
), "Basic distance calculation failed."
|
|
403
|
+
|
|
404
|
+
# Empty fields
|
|
405
|
+
record1 = ["hello", ""]
|
|
406
|
+
record2 = ["hullo", ""]
|
|
407
|
+
weights = [1.0, 1.0]
|
|
408
|
+
|
|
409
|
+
# Expected distance based on simple Levenshtein distances
|
|
410
|
+
# Levenshtein("hello", "hullo") = 1, Levenshtein("", "") = 0
|
|
411
|
+
expected_distance = (1 / 5 * 1.0 + 0 * 1.0) / 2 # Averaged over two fields
|
|
412
|
+
|
|
413
|
+
result = calculate_levenshtein_distance(record1, record2, weights)
|
|
414
|
+
assert result == pytest.approx(
|
|
415
|
+
expected_distance
|
|
416
|
+
), "Basic distance calculation with empty fields failed."
|
|
417
|
+
|
|
418
|
+
# Partial empty fields
|
|
419
|
+
record1 = ["hello", "world"]
|
|
420
|
+
record2 = ["hullo", ""]
|
|
421
|
+
weights = [1.0, 1.0]
|
|
422
|
+
|
|
423
|
+
# Expected distance based on simple Levenshtein distances
|
|
424
|
+
# Levenshtein("hello", "hullo") = 1, Levenshtein("world", "") = 5
|
|
425
|
+
expected_distance = (
|
|
426
|
+
1 / 5 * 1.0 + 5 / 5 * 0.05 * 1.0
|
|
427
|
+
) / 2 # Averaged over two fields
|
|
428
|
+
|
|
429
|
+
result = calculate_levenshtein_distance(record1, record2, weights)
|
|
430
|
+
assert result == pytest.approx(
|
|
431
|
+
expected_distance
|
|
432
|
+
), "Basic distance calculation with partial empty fields failed."
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_calculate_levenshtein_distance_weighted():
|
|
436
|
+
record1 = ["cat", "dog"]
|
|
437
|
+
record2 = ["bat", "fog"]
|
|
438
|
+
weights = [2.0, 0.5]
|
|
439
|
+
|
|
440
|
+
# Levenshtein("cat", "bat") = 1, Levenshtein("dog", "fog") = 1
|
|
441
|
+
expected_distance = (
|
|
442
|
+
1 / 3 * 2.0 + 1 / 3 * 0.5
|
|
443
|
+
) / 2.5 # Weighted average over two fields
|
|
444
|
+
|
|
445
|
+
result = calculate_levenshtein_distance(record1, record2, weights)
|
|
446
|
+
assert result == pytest.approx(
|
|
447
|
+
expected_distance
|
|
448
|
+
), "Weighted distance calculation failed."
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def test_calculate_levenshtein_distance_records_length_doesnt_match():
|
|
452
|
+
record1 = ["cat", "dog", "cow"]
|
|
453
|
+
record2 = ["bat", "fog"]
|
|
454
|
+
weights = [2.0, 0.5]
|
|
455
|
+
|
|
456
|
+
with pytest.raises(ValueError) as e:
|
|
457
|
+
calculate_levenshtein_distance(record1, record2, weights)
|
|
458
|
+
assert "Records must have the same number of fields." in str(e.value)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_calculate_levenshtein_distance_weights_length_doesnt_match():
|
|
462
|
+
record1 = ["cat", "dog"]
|
|
463
|
+
record2 = ["bat", "fog"]
|
|
464
|
+
weights = [2.0, 0.5, 3.0]
|
|
465
|
+
|
|
466
|
+
with pytest.raises(ValueError) as e:
|
|
467
|
+
calculate_levenshtein_distance(record1, record2, weights)
|
|
468
|
+
assert "Records must be same size as fields (weights)." in str(e.value)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
@pytest.mark.skipif(
|
|
472
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
473
|
+
reason="requires optional dependencies for annoy",
|
|
474
|
+
)
|
|
475
|
+
def test_all_numeric_columns():
|
|
476
|
+
df_db = pd.DataFrame({"A": ["1", "2", "3"], "B": ["4.5", " 5.5", "6.5"]})
|
|
477
|
+
df_query = pd.DataFrame({"A": ["4", "5", ""], "B": ["4.5", "5.5", "6.5"]})
|
|
478
|
+
weights = [0.1, 0.2]
|
|
479
|
+
expected_output = (
|
|
480
|
+
["A", "B"], # numerical_features
|
|
481
|
+
[], # boolean_features
|
|
482
|
+
[], # categorical_features
|
|
483
|
+
[0.1, 0.2], # numerical_weights
|
|
484
|
+
[], # boolean_weights
|
|
485
|
+
[], # categorical_weights
|
|
486
|
+
)
|
|
487
|
+
assert determine_field_types(df_db, df_query, weights) == expected_output
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@pytest.mark.skipif(
|
|
491
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
492
|
+
reason="requires optional dependencies for annoy",
|
|
493
|
+
)
|
|
494
|
+
def test_numeric_columns__one_non_numeric():
|
|
495
|
+
df_db = pd.DataFrame({"A": ["1", "2", "3"], "B": ["4.5", "5.5", "6.5"]})
|
|
496
|
+
df_query = pd.DataFrame({"A": ["4", "5", "6"], "B": ["abcd", "5.5", "6.5"]})
|
|
497
|
+
weights = [0.1, 0.2]
|
|
498
|
+
expected_output = (
|
|
499
|
+
["A"], # numerical_features
|
|
500
|
+
[], # boolean_features
|
|
501
|
+
["B"], # categorical_features
|
|
502
|
+
[0.1], # numerical_weights
|
|
503
|
+
[], # boolean_weights
|
|
504
|
+
[0.2], # categorical_weights
|
|
505
|
+
)
|
|
506
|
+
assert determine_field_types(df_db, df_query, weights) == expected_output
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@pytest.mark.skipif(
|
|
510
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
511
|
+
reason="requires optional dependencies for annoy",
|
|
512
|
+
)
|
|
513
|
+
def test_all_boolean_columns():
|
|
514
|
+
df_db = pd.DataFrame(
|
|
515
|
+
{"A": ["true", "false", "true"], "B": ["false", "true", "false"]}
|
|
516
|
+
)
|
|
517
|
+
df_query = pd.DataFrame(
|
|
518
|
+
{"A": ["true", "false", "true"], "B": ["false", "true", "false"]}
|
|
519
|
+
)
|
|
520
|
+
weights = [0.3, 0.4]
|
|
521
|
+
expected_output = (
|
|
522
|
+
[], # numerical_features
|
|
523
|
+
["A", "B"], # boolean_features
|
|
524
|
+
[], # categorical_features
|
|
525
|
+
[], # numerical_weights
|
|
526
|
+
[0.3, 0.4], # boolean_weights
|
|
527
|
+
[], # categorical_weights
|
|
528
|
+
)
|
|
529
|
+
assert determine_field_types(df_db, df_query, weights) == expected_output
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
@pytest.mark.skipif(
|
|
533
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
534
|
+
reason="requires optional dependencies for annoy",
|
|
535
|
+
)
|
|
536
|
+
def test_all_categorical_columns():
|
|
537
|
+
df_db = pd.DataFrame(
|
|
538
|
+
{"A": ["apple", "banana", "cherry"], "B": ["dog", "cat", "mouse"]}
|
|
539
|
+
)
|
|
540
|
+
df_query = pd.DataFrame(
|
|
541
|
+
{"A": ["banana", "apple", "cherry"], "B": ["cat", "dog", "mouse"]}
|
|
542
|
+
)
|
|
543
|
+
weights = [0.5, 0.6]
|
|
544
|
+
expected_output = (
|
|
545
|
+
[], # numerical_features
|
|
546
|
+
[], # boolean_features
|
|
547
|
+
["A", "B"], # categorical_features
|
|
548
|
+
[], # numerical_weights
|
|
549
|
+
[], # boolean_weights
|
|
550
|
+
[0.5, 0.6], # categorical_weights
|
|
551
|
+
)
|
|
552
|
+
assert determine_field_types(df_db, df_query, weights) == expected_output
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@pytest.mark.skipif(
|
|
556
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
557
|
+
reason="requires optional dependencies for annoy",
|
|
558
|
+
)
|
|
559
|
+
def test_mixed_types():
|
|
560
|
+
df_db = pd.DataFrame(
|
|
561
|
+
{
|
|
562
|
+
"A": ["1", "2", "3"],
|
|
563
|
+
"B": ["true", "false", "true"],
|
|
564
|
+
"C": ["apple", "banana", "cherry"],
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
df_query = pd.DataFrame(
|
|
568
|
+
{
|
|
569
|
+
"A": ["1", "3", ""],
|
|
570
|
+
"B": ["true", "true", "true"],
|
|
571
|
+
"C": ["apple", "", "3"],
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
weights = [0.7, 0.8, 0.9]
|
|
575
|
+
expected_output = (
|
|
576
|
+
["A"], # numerical_features
|
|
577
|
+
["B"], # boolean_features
|
|
578
|
+
["C"], # categorical_features
|
|
579
|
+
[0.7], # numerical_weights
|
|
580
|
+
[0.8], # boolean_weights
|
|
581
|
+
[0.9], # categorical_weights
|
|
582
|
+
)
|
|
583
|
+
assert determine_field_types(df_db, df_query, weights) == expected_output
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@pytest.mark.skipif(
|
|
587
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
588
|
+
reason="requires optional dependencies for annoy",
|
|
589
|
+
)
|
|
590
|
+
def test_vectorize_records_mixed_numerical_boolean_categorical():
|
|
591
|
+
# Test data with mixed types: numerical and categorical only
|
|
592
|
+
db_records = [["1.0", "true", "apple"], ["2.0", "false", "banana"]]
|
|
593
|
+
query_records = [["1.5", "true", "apple"], ["2.5", "false", "cherry"]]
|
|
594
|
+
weights = [1.0, 1.0, 1.0] # Equal weights for numerical and categorical columns
|
|
595
|
+
hash_features = 4 # Number of hashing vectorizer features for categorical columns
|
|
596
|
+
|
|
597
|
+
final_db_vectors, final_query_vectors = vectorize_records(
|
|
598
|
+
db_records, query_records, hash_features, weights
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
# Check the shape of the output vectors
|
|
602
|
+
assert final_db_vectors.shape[0] == len(db_records), "DB vectors row count mismatch"
|
|
603
|
+
assert final_query_vectors.shape[0] == len(
|
|
604
|
+
query_records
|
|
605
|
+
), "Query vectors row count mismatch"
|
|
606
|
+
|
|
607
|
+
# Expected dimensions: numerical (1) + categorical hashed features (4)
|
|
608
|
+
expected_feature_count = 2 + hash_features
|
|
609
|
+
assert (
|
|
610
|
+
final_db_vectors.shape[1] == expected_feature_count
|
|
611
|
+
), "DB vectors column count mismatch"
|
|
612
|
+
assert (
|
|
613
|
+
final_query_vectors.shape[1] == expected_feature_count
|
|
614
|
+
), "Query vectors column count mismatch"
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
@pytest.mark.skipif(
|
|
618
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
619
|
+
reason="requires optional dependencies for annoy",
|
|
620
|
+
)
|
|
621
|
+
def test_annoy_post_process():
|
|
622
|
+
# Test data
|
|
623
|
+
load_records = [["Alice", "Engineer"], ["Bob", "Doctor"]]
|
|
624
|
+
query_records = [["q1", "Alice", "Engineer"], ["q2", "Charlie", "Artist"]]
|
|
625
|
+
weights = [1.0, 1.0, 1.0] # Example weights
|
|
626
|
+
|
|
627
|
+
closest_records, insert_records = annoy_post_process(
|
|
628
|
+
load_records=load_records,
|
|
629
|
+
query_records=query_records,
|
|
630
|
+
similarity_weights=weights,
|
|
631
|
+
all_fields=["Name", "Occupation"],
|
|
632
|
+
threshold=None,
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
# Assert the closest records
|
|
636
|
+
assert (
|
|
637
|
+
len(closest_records) == 2
|
|
638
|
+
) # We expect two results (one for each query record)
|
|
639
|
+
assert (
|
|
640
|
+
closest_records[0]["id"] == "q1"
|
|
641
|
+
) # The first query record should match the first load record
|
|
642
|
+
|
|
643
|
+
# No errors expected
|
|
644
|
+
assert not insert_records
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@pytest.mark.skipif(
|
|
648
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
649
|
+
reason="requires optional dependencies for annoy",
|
|
650
|
+
)
|
|
651
|
+
def test_annoy_post_process__insert_records():
|
|
652
|
+
# Test data
|
|
653
|
+
load_records = [["Alice", "Engineer"], ["Bob", "Doctor"]]
|
|
654
|
+
query_records = [["q1", "Alice", "Engineer"], ["q2", "Charlie", "Artist"]]
|
|
655
|
+
weights = [1.0, 1.0, 1.0] # Example weights
|
|
656
|
+
threshold = 0.3
|
|
657
|
+
|
|
658
|
+
closest_records, insert_records = annoy_post_process(
|
|
659
|
+
load_records=load_records,
|
|
660
|
+
query_records=query_records,
|
|
661
|
+
similarity_weights=weights,
|
|
662
|
+
all_fields=["Name", "Occupation"],
|
|
663
|
+
threshold=threshold,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Assert the closest records
|
|
667
|
+
assert len(closest_records) == 2 # We expect two results (one for each load record)
|
|
668
|
+
|
|
669
|
+
# Count matches vs insertions
|
|
670
|
+
matches = [record for record in closest_records if record is not None]
|
|
671
|
+
insertions = [record for record in closest_records if record is None]
|
|
672
|
+
|
|
673
|
+
# We should have some matches or insertions
|
|
674
|
+
assert len(matches) + len(insertions) == 2
|
|
675
|
+
|
|
676
|
+
# Check that matches have the correct structure
|
|
677
|
+
for match in matches:
|
|
678
|
+
assert "id" in match
|
|
679
|
+
assert match["success"] is True
|
|
680
|
+
assert match["created"] is False
|
|
681
|
+
assert match["id"] in ["q1", "q2"]
|
|
682
|
+
|
|
683
|
+
# The number of insertions should match the number of None values in closest_records
|
|
684
|
+
assert len(insert_records) == len(insertions)
|
|
685
|
+
|
|
686
|
+
# Each insertion record should match the structure expected
|
|
687
|
+
for insert_record in insert_records:
|
|
688
|
+
assert len(insert_record) == 2 # Name and Occupation
|
|
689
|
+
assert insert_record in [["Alice", "Engineer"], ["Bob", "Doctor"]]
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def test_annoy_post_process__no_query_records():
|
|
693
|
+
# Test data
|
|
694
|
+
load_records = [["Alice", "Engineer"], ["Bob", "Doctor"]]
|
|
695
|
+
query_records = []
|
|
696
|
+
weights = [1.0, 1.0, 1.0] # Example weights
|
|
697
|
+
threshold = 0.3
|
|
698
|
+
|
|
699
|
+
closest_records, insert_records = annoy_post_process(
|
|
700
|
+
load_records=load_records,
|
|
701
|
+
query_records=query_records,
|
|
702
|
+
similarity_weights=weights,
|
|
703
|
+
all_fields=["Name", "Occupation"],
|
|
704
|
+
threshold=threshold,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
# Assert the closest records
|
|
708
|
+
assert len(closest_records) == 2 # We expect two results (both None)
|
|
709
|
+
assert all(rec is None for rec in closest_records) # Both should be None
|
|
710
|
+
assert insert_records[0] == [
|
|
711
|
+
"Alice",
|
|
712
|
+
"Engineer",
|
|
713
|
+
] # The first insert record should match the second load record
|
|
714
|
+
assert insert_records[1] == [
|
|
715
|
+
"Bob",
|
|
716
|
+
"Doctor",
|
|
717
|
+
] # The first insert record should match the second load record
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@pytest.mark.skipif(
|
|
721
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
722
|
+
reason="requires optional dependencies for annoy",
|
|
723
|
+
)
|
|
724
|
+
def test_annoy_post_process__insert_records_with_polymorphic_fields():
|
|
725
|
+
# Test data
|
|
726
|
+
load_records = [
|
|
727
|
+
["Alice", "Engineer", "Alice_Contact", "abcd1234"],
|
|
728
|
+
["Bob", "Doctor", "Bob_Contact", "qwer1234"],
|
|
729
|
+
]
|
|
730
|
+
query_records = [
|
|
731
|
+
["q1", "Alice", "Engineer", "Alice_Contact"],
|
|
732
|
+
["q2", "Charlie", "Artist", "Charlie_Contact"],
|
|
733
|
+
]
|
|
734
|
+
weights = [1.0, 1.0, 1.0, 1.0] # Example weights
|
|
735
|
+
threshold = 0.3
|
|
736
|
+
all_fields = ["Name", "Occupation", "Contact.Name", "ContactId"]
|
|
737
|
+
|
|
738
|
+
closest_records, insert_records = annoy_post_process(
|
|
739
|
+
load_records=load_records,
|
|
740
|
+
query_records=query_records,
|
|
741
|
+
similarity_weights=weights,
|
|
742
|
+
all_fields=all_fields,
|
|
743
|
+
threshold=threshold,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
# Assert the closest records
|
|
747
|
+
assert len(closest_records) == 2 # We expect two results (one for each load record)
|
|
748
|
+
|
|
749
|
+
# Count matches vs insertions
|
|
750
|
+
matches = [record for record in closest_records if record is not None]
|
|
751
|
+
insertions = [record for record in closest_records if record is None]
|
|
752
|
+
|
|
753
|
+
# We should have some matches or insertions
|
|
754
|
+
assert len(matches) + len(insertions) == 2
|
|
755
|
+
|
|
756
|
+
# Check that matches have the correct structure
|
|
757
|
+
for match in matches:
|
|
758
|
+
assert "id" in match
|
|
759
|
+
assert match["success"] is True
|
|
760
|
+
assert match["created"] is False
|
|
761
|
+
assert match["id"] in ["q1", "q2"]
|
|
762
|
+
|
|
763
|
+
# The number of insertions should match the number of None values in closest_records
|
|
764
|
+
assert len(insert_records) == len(insertions)
|
|
765
|
+
|
|
766
|
+
# Each insertion record should have the polymorphic field filtered out
|
|
767
|
+
# (ContactId should be removed, but Contact.Name lookup field should remain)
|
|
768
|
+
for insert_record in insert_records:
|
|
769
|
+
assert len(insert_record) == 3 # Name, Occupation, ContactId (load fields)
|
|
770
|
+
# Should be one of the original load records but with ContactId field
|
|
771
|
+
assert insert_record in [
|
|
772
|
+
["Alice", "Engineer", "abcd1234"],
|
|
773
|
+
["Bob", "Doctor", "qwer1234"],
|
|
774
|
+
]
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
@pytest.mark.skipif(
|
|
778
|
+
not PANDAS_AVAILABLE or not OPTIONAL_DEPENDENCIES_AVAILABLE,
|
|
779
|
+
reason="requires optional dependencies for annoy",
|
|
780
|
+
)
|
|
781
|
+
def test_single_record_match_annoy_post_process():
|
|
782
|
+
# Mock data where only the first query record matches the first load record
|
|
783
|
+
load_records = [["Alice", "Engineer"], ["Bob", "Doctor"]]
|
|
784
|
+
query_records = [["q1", "Alice", "Engineer"]]
|
|
785
|
+
weights = [1.0, 1.0, 1.0]
|
|
786
|
+
|
|
787
|
+
closest_records, insert_records = annoy_post_process(
|
|
788
|
+
load_records=load_records,
|
|
789
|
+
query_records=query_records,
|
|
790
|
+
similarity_weights=weights,
|
|
791
|
+
all_fields=["Name", "Occupation"],
|
|
792
|
+
threshold=None,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
# Both the load records should be matched with the only query record we have
|
|
796
|
+
assert len(closest_records) == 2
|
|
797
|
+
assert closest_records[0]["id"] == "q1"
|
|
798
|
+
assert not insert_records
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
@pytest.mark.parametrize(
|
|
802
|
+
"filter_clause, limit_clause, offset_clause, expected",
|
|
803
|
+
[
|
|
804
|
+
# Test: No existing LIMIT/OFFSET and no new clauses
|
|
805
|
+
("SELECT * FROM users", None, None, " SELECT * FROM users"),
|
|
806
|
+
# Test: Existing LIMIT and no new limit provided
|
|
807
|
+
("SELECT * FROM users LIMIT 100", None, None, "SELECT * FROM users LIMIT 100"),
|
|
808
|
+
# Test: Existing OFFSET and no new offset provided
|
|
809
|
+
("SELECT * FROM users OFFSET 20", None, None, "SELECT * FROM users OFFSET 20"),
|
|
810
|
+
# Test: Existing LIMIT/OFFSET and new clauses provided
|
|
811
|
+
(
|
|
812
|
+
"SELECT * FROM users LIMIT 100 OFFSET 20",
|
|
813
|
+
50,
|
|
814
|
+
10,
|
|
815
|
+
"SELECT * FROM users LIMIT 50 OFFSET 30",
|
|
816
|
+
),
|
|
817
|
+
# Test: Existing LIMIT, new limit larger than existing (should keep the smaller one)
|
|
818
|
+
("SELECT * FROM users LIMIT 100", 150, None, "SELECT * FROM users LIMIT 100"),
|
|
819
|
+
# Test: New limit smaller than existing (should use the new one)
|
|
820
|
+
("SELECT * FROM users LIMIT 100", 50, None, "SELECT * FROM users LIMIT 50"),
|
|
821
|
+
# Test: Existing OFFSET, adding a new offset (should sum the offsets)
|
|
822
|
+
("SELECT * FROM users OFFSET 20", None, 30, "SELECT * FROM users OFFSET 50"),
|
|
823
|
+
# Test: Existing LIMIT/OFFSET and new values set to None
|
|
824
|
+
(
|
|
825
|
+
"SELECT * FROM users LIMIT 100 OFFSET 20",
|
|
826
|
+
None,
|
|
827
|
+
None,
|
|
828
|
+
"SELECT * FROM users LIMIT 100 OFFSET 20",
|
|
829
|
+
),
|
|
830
|
+
# Test: Removing existing LIMIT and adding a new one
|
|
831
|
+
("SELECT * FROM users LIMIT 200", 50, None, "SELECT * FROM users LIMIT 50"),
|
|
832
|
+
# Test: Removing existing OFFSET and adding a new one
|
|
833
|
+
("SELECT * FROM users OFFSET 40", None, 20, "SELECT * FROM users OFFSET 60"),
|
|
834
|
+
# Edge case: Filter clause with mixed cases
|
|
835
|
+
(
|
|
836
|
+
"SELECT * FROM users LiMiT 100 oFfSeT 20",
|
|
837
|
+
50,
|
|
838
|
+
10,
|
|
839
|
+
"SELECT * FROM users LIMIT 50 OFFSET 30",
|
|
840
|
+
),
|
|
841
|
+
# Test: Filter clause with trailing/leading spaces
|
|
842
|
+
(
|
|
843
|
+
" SELECT * FROM users LIMIT 100 OFFSET 20 ",
|
|
844
|
+
50,
|
|
845
|
+
10,
|
|
846
|
+
"SELECT * FROM users LIMIT 50 OFFSET 30",
|
|
847
|
+
),
|
|
848
|
+
],
|
|
849
|
+
)
|
|
850
|
+
def test_add_limit_offset_to_user_filter(
|
|
851
|
+
filter_clause, limit_clause, offset_clause, expected
|
|
852
|
+
):
|
|
853
|
+
result = add_limit_offset_to_user_filter(filter_clause, limit_clause, offset_clause)
|
|
854
|
+
assert result.strip() == expected.strip()
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
def test_reorder_records_basic_reordering():
|
|
858
|
+
records = [
|
|
859
|
+
["Alice", 30, "Engineer"],
|
|
860
|
+
["Bob", 25, "Designer"],
|
|
861
|
+
]
|
|
862
|
+
original_fields = ["name", "age", "job"]
|
|
863
|
+
new_fields = ["job", "name"]
|
|
864
|
+
|
|
865
|
+
expected = [
|
|
866
|
+
["Engineer", "Alice"],
|
|
867
|
+
["Designer", "Bob"],
|
|
868
|
+
]
|
|
869
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
870
|
+
assert result == expected
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def test_reorder_records_partial_fields():
|
|
874
|
+
records = [
|
|
875
|
+
["Alice", 30, "Engineer"],
|
|
876
|
+
["Bob", 25, "Designer"],
|
|
877
|
+
]
|
|
878
|
+
original_fields = ["name", "age", "job"]
|
|
879
|
+
new_fields = ["age"]
|
|
880
|
+
|
|
881
|
+
expected = [
|
|
882
|
+
[30],
|
|
883
|
+
[25],
|
|
884
|
+
]
|
|
885
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
886
|
+
assert result == expected
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
def test_reorder_records_missing_fields_in_new_fields():
|
|
890
|
+
records = [
|
|
891
|
+
["Alice", 30, "Engineer"],
|
|
892
|
+
["Bob", 25, "Designer"],
|
|
893
|
+
]
|
|
894
|
+
original_fields = ["name", "age", "job"]
|
|
895
|
+
new_fields = ["nonexistent", "job"]
|
|
896
|
+
|
|
897
|
+
expected = [
|
|
898
|
+
["Engineer"],
|
|
899
|
+
["Designer"],
|
|
900
|
+
]
|
|
901
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
902
|
+
assert result == expected
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def test_reorder_records_empty_records():
|
|
906
|
+
records = []
|
|
907
|
+
original_fields = ["name", "age", "job"]
|
|
908
|
+
new_fields = ["job", "name"]
|
|
909
|
+
|
|
910
|
+
expected = []
|
|
911
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
912
|
+
assert result == expected
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def test_reorder_records_empty_new_fields():
|
|
916
|
+
records = [
|
|
917
|
+
["Alice", 30, "Engineer"],
|
|
918
|
+
["Bob", 25, "Designer"],
|
|
919
|
+
]
|
|
920
|
+
original_fields = ["name", "age", "job"]
|
|
921
|
+
new_fields = []
|
|
922
|
+
|
|
923
|
+
expected = [
|
|
924
|
+
[],
|
|
925
|
+
[],
|
|
926
|
+
]
|
|
927
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
928
|
+
assert result == expected
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
def test_reorder_records_empty_original_fields():
|
|
932
|
+
records = [
|
|
933
|
+
["Alice", 30, "Engineer"],
|
|
934
|
+
["Bob", 25, "Designer"],
|
|
935
|
+
]
|
|
936
|
+
original_fields = []
|
|
937
|
+
new_fields = ["job", "name"]
|
|
938
|
+
|
|
939
|
+
with pytest.raises(KeyError):
|
|
940
|
+
reorder_records(records, original_fields, new_fields)
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
def test_reorder_records_no_common_fields():
|
|
944
|
+
records = [
|
|
945
|
+
["Alice", 30, "Engineer"],
|
|
946
|
+
["Bob", 25, "Designer"],
|
|
947
|
+
]
|
|
948
|
+
original_fields = ["name", "age", "job"]
|
|
949
|
+
new_fields = ["nonexistent_field"]
|
|
950
|
+
|
|
951
|
+
expected = [
|
|
952
|
+
[],
|
|
953
|
+
[],
|
|
954
|
+
]
|
|
955
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
956
|
+
assert result == expected
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def test_reorder_records_duplicate_fields_in_new_fields():
|
|
960
|
+
records = [
|
|
961
|
+
["Alice", 30, "Engineer"],
|
|
962
|
+
["Bob", 25, "Designer"],
|
|
963
|
+
]
|
|
964
|
+
original_fields = ["name", "age", "job"]
|
|
965
|
+
new_fields = ["job", "job", "name"]
|
|
966
|
+
|
|
967
|
+
expected = [
|
|
968
|
+
["Engineer", "Engineer", "Alice"],
|
|
969
|
+
["Designer", "Designer", "Bob"],
|
|
970
|
+
]
|
|
971
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
972
|
+
assert result == expected
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def test_reorder_records_all_fields_in_order():
|
|
976
|
+
records = [
|
|
977
|
+
["Alice", 30, "Engineer"],
|
|
978
|
+
["Bob", 25, "Designer"],
|
|
979
|
+
]
|
|
980
|
+
original_fields = ["name", "age", "job"]
|
|
981
|
+
new_fields = ["name", "age", "job"]
|
|
982
|
+
|
|
983
|
+
expected = [
|
|
984
|
+
["Alice", 30, "Engineer"],
|
|
985
|
+
["Bob", 25, "Designer"],
|
|
986
|
+
]
|
|
987
|
+
result = reorder_records(records, original_fields, new_fields)
|
|
988
|
+
assert result == expected
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def test_split_and_filter_fields_basic_case():
|
|
992
|
+
fields = [
|
|
993
|
+
"Account.Name",
|
|
994
|
+
"Account.Industry",
|
|
995
|
+
"Contact.Name",
|
|
996
|
+
"AccountId",
|
|
997
|
+
"ContactId",
|
|
998
|
+
"CreatedDate",
|
|
999
|
+
]
|
|
1000
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1001
|
+
assert load_fields == ["AccountId", "ContactId", "CreatedDate"]
|
|
1002
|
+
assert select_fields == [
|
|
1003
|
+
"Account.Name",
|
|
1004
|
+
"Account.Industry",
|
|
1005
|
+
"Contact.Name",
|
|
1006
|
+
"CreatedDate",
|
|
1007
|
+
]
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def test_split_and_filter_fields_all_non_lookup_fields():
|
|
1011
|
+
fields = ["Name", "CreatedDate"]
|
|
1012
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1013
|
+
assert load_fields == ["Name", "CreatedDate"]
|
|
1014
|
+
assert select_fields == fields
|
|
1015
|
+
|
|
1016
|
+
|
|
1017
|
+
def test_split_and_filter_fields_all_lookup_fields():
|
|
1018
|
+
fields = ["Account.Name", "Account.Industry", "Contact.Name"]
|
|
1019
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1020
|
+
assert load_fields == []
|
|
1021
|
+
assert select_fields == fields
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
def test_split_and_filter_fields_empty_fields():
|
|
1025
|
+
fields = []
|
|
1026
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1027
|
+
assert load_fields == []
|
|
1028
|
+
assert select_fields == []
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def test_split_and_filter_fields_single_non_lookup_field():
|
|
1032
|
+
fields = ["Id"]
|
|
1033
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1034
|
+
assert load_fields == ["Id"]
|
|
1035
|
+
assert select_fields == ["Id"]
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def test_split_and_filter_fields_single_lookup_field():
|
|
1039
|
+
fields = ["Account.Name"]
|
|
1040
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1041
|
+
assert load_fields == []
|
|
1042
|
+
assert select_fields == ["Account.Name"]
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def test_split_and_filter_fields_multiple_unique_lookups():
|
|
1046
|
+
fields = [
|
|
1047
|
+
"Account.Name",
|
|
1048
|
+
"Account.Industry",
|
|
1049
|
+
"Contact.Email",
|
|
1050
|
+
"Contact.Phone",
|
|
1051
|
+
"Id",
|
|
1052
|
+
]
|
|
1053
|
+
load_fields, select_fields = split_and_filter_fields(fields)
|
|
1054
|
+
assert load_fields == ["Id"]
|
|
1055
|
+
assert (
|
|
1056
|
+
select_fields == fields
|
|
1057
|
+
) # No filtering applied since all components are unique
|