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.

Files changed (744) hide show
  1. cumulusci/__about__.py +1 -0
  2. cumulusci/__init__.py +22 -0
  3. cumulusci/__main__.py +3 -0
  4. cumulusci/cli/__init__.py +0 -0
  5. cumulusci/cli/cci.py +244 -0
  6. cumulusci/cli/error.py +125 -0
  7. cumulusci/cli/flow.py +185 -0
  8. cumulusci/cli/logger.py +72 -0
  9. cumulusci/cli/org.py +692 -0
  10. cumulusci/cli/plan.py +181 -0
  11. cumulusci/cli/project.py +391 -0
  12. cumulusci/cli/robot.py +116 -0
  13. cumulusci/cli/runtime.py +190 -0
  14. cumulusci/cli/service.py +521 -0
  15. cumulusci/cli/task.py +295 -0
  16. cumulusci/cli/tests/__init__.py +0 -0
  17. cumulusci/cli/tests/test_cci.py +545 -0
  18. cumulusci/cli/tests/test_error.py +170 -0
  19. cumulusci/cli/tests/test_flow.py +276 -0
  20. cumulusci/cli/tests/test_logger.py +25 -0
  21. cumulusci/cli/tests/test_org.py +1438 -0
  22. cumulusci/cli/tests/test_plan.py +245 -0
  23. cumulusci/cli/tests/test_project.py +235 -0
  24. cumulusci/cli/tests/test_robot.py +177 -0
  25. cumulusci/cli/tests/test_runtime.py +197 -0
  26. cumulusci/cli/tests/test_service.py +853 -0
  27. cumulusci/cli/tests/test_task.py +266 -0
  28. cumulusci/cli/tests/test_ui.py +310 -0
  29. cumulusci/cli/tests/test_utils.py +122 -0
  30. cumulusci/cli/tests/utils.py +52 -0
  31. cumulusci/cli/ui.py +234 -0
  32. cumulusci/cli/utils.py +150 -0
  33. cumulusci/conftest.py +181 -0
  34. cumulusci/core/__init__.py +0 -0
  35. cumulusci/core/config/BaseConfig.py +5 -0
  36. cumulusci/core/config/BaseTaskFlowConfig.py +5 -0
  37. cumulusci/core/config/OrgConfig.py +5 -0
  38. cumulusci/core/config/ScratchOrgConfig.py +5 -0
  39. cumulusci/core/config/__init__.py +125 -0
  40. cumulusci/core/config/base_config.py +111 -0
  41. cumulusci/core/config/base_task_flow_config.py +82 -0
  42. cumulusci/core/config/marketing_cloud_service_config.py +83 -0
  43. cumulusci/core/config/oauth2_service_config.py +17 -0
  44. cumulusci/core/config/org_config.py +604 -0
  45. cumulusci/core/config/project_config.py +782 -0
  46. cumulusci/core/config/scratch_org_config.py +251 -0
  47. cumulusci/core/config/sfdx_org_config.py +220 -0
  48. cumulusci/core/config/tests/_test_config_backwards_compatibility.py +33 -0
  49. cumulusci/core/config/tests/test_config.py +1895 -0
  50. cumulusci/core/config/tests/test_config_expensive.py +839 -0
  51. cumulusci/core/config/tests/test_config_util.py +91 -0
  52. cumulusci/core/config/universal_config.py +88 -0
  53. cumulusci/core/config/util.py +18 -0
  54. cumulusci/core/datasets.py +303 -0
  55. cumulusci/core/debug.py +33 -0
  56. cumulusci/core/dependencies/__init__.py +55 -0
  57. cumulusci/core/dependencies/base.py +561 -0
  58. cumulusci/core/dependencies/dependencies.py +273 -0
  59. cumulusci/core/dependencies/github.py +177 -0
  60. cumulusci/core/dependencies/github_resolvers.py +244 -0
  61. cumulusci/core/dependencies/resolvers.py +580 -0
  62. cumulusci/core/dependencies/tests/__init__.py +0 -0
  63. cumulusci/core/dependencies/tests/conftest.py +385 -0
  64. cumulusci/core/dependencies/tests/test_dependencies.py +950 -0
  65. cumulusci/core/dependencies/tests/test_github.py +83 -0
  66. cumulusci/core/dependencies/tests/test_resolvers.py +1027 -0
  67. cumulusci/core/dependencies/utils.py +13 -0
  68. cumulusci/core/enums.py +11 -0
  69. cumulusci/core/exceptions.py +311 -0
  70. cumulusci/core/flowrunner.py +888 -0
  71. cumulusci/core/github.py +665 -0
  72. cumulusci/core/keychain/__init__.py +24 -0
  73. cumulusci/core/keychain/base_project_keychain.py +441 -0
  74. cumulusci/core/keychain/encrypted_file_project_keychain.py +945 -0
  75. cumulusci/core/keychain/environment_project_keychain.py +7 -0
  76. cumulusci/core/keychain/serialization.py +152 -0
  77. cumulusci/core/keychain/subprocess_keychain.py +24 -0
  78. cumulusci/core/keychain/tests/conftest.py +50 -0
  79. cumulusci/core/keychain/tests/test_base_project_keychain.py +299 -0
  80. cumulusci/core/keychain/tests/test_encrypted_file_project_keychain.py +1228 -0
  81. cumulusci/core/metadeploy/__init__.py +0 -0
  82. cumulusci/core/metadeploy/api.py +88 -0
  83. cumulusci/core/metadeploy/plans.py +25 -0
  84. cumulusci/core/metadeploy/tests/test_api.py +276 -0
  85. cumulusci/core/runtime.py +115 -0
  86. cumulusci/core/sfdx.py +162 -0
  87. cumulusci/core/source/__init__.py +16 -0
  88. cumulusci/core/source/github.py +50 -0
  89. cumulusci/core/source/local_folder.py +35 -0
  90. cumulusci/core/source_transforms/__init__.py +0 -0
  91. cumulusci/core/source_transforms/tests/test_transforms.py +1091 -0
  92. cumulusci/core/source_transforms/transforms.py +532 -0
  93. cumulusci/core/tasks.py +404 -0
  94. cumulusci/core/template_utils.py +59 -0
  95. cumulusci/core/tests/__init__.py +0 -0
  96. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_e2e.yaml +215 -0
  97. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_extract_standard_objects.yaml +199 -0
  98. cumulusci/core/tests/cassettes/TestDatasetsE2E.test_datasets_read_explicit_extract_declaration.yaml +3 -0
  99. cumulusci/core/tests/fake_remote_repo/cumulusci.yml +32 -0
  100. cumulusci/core/tests/fake_remote_repo/tasks/directory/example_2.py +6 -0
  101. cumulusci/core/tests/fake_remote_repo/tasks/example.py +43 -0
  102. cumulusci/core/tests/fake_remote_repo_2/cumulusci.yml +11 -0
  103. cumulusci/core/tests/fake_remote_repo_2/tasks/example_3.py +6 -0
  104. cumulusci/core/tests/test_datasets_e2e.py +386 -0
  105. cumulusci/core/tests/test_exceptions.py +11 -0
  106. cumulusci/core/tests/test_flowrunner.py +836 -0
  107. cumulusci/core/tests/test_github.py +942 -0
  108. cumulusci/core/tests/test_sfdx.py +138 -0
  109. cumulusci/core/tests/test_source.py +678 -0
  110. cumulusci/core/tests/test_tasks.py +262 -0
  111. cumulusci/core/tests/test_utils.py +141 -0
  112. cumulusci/core/tests/test_utils_merge_config.py +276 -0
  113. cumulusci/core/tests/test_versions.py +76 -0
  114. cumulusci/core/tests/untrusted_repo_child/cumulusci.yml +7 -0
  115. cumulusci/core/tests/untrusted_repo_child/tasks/untrusted_child.py +6 -0
  116. cumulusci/core/tests/untrusted_repo_parent/cumulusci.yml +26 -0
  117. cumulusci/core/tests/untrusted_repo_parent/tasks/untrusted_parent.py +6 -0
  118. cumulusci/core/tests/utils.py +116 -0
  119. cumulusci/core/tests/yaml/global.yaml +0 -0
  120. cumulusci/core/utils.py +402 -0
  121. cumulusci/core/versions.py +149 -0
  122. cumulusci/cumulusci.yml +1621 -0
  123. cumulusci/files/admin_profile.xml +20 -0
  124. cumulusci/files/delete_excludes.txt +424 -0
  125. cumulusci/files/templates/project/README.md +12 -0
  126. cumulusci/files/templates/project/cumulusci.yml +63 -0
  127. cumulusci/files/templates/project/dot-gitignore +60 -0
  128. cumulusci/files/templates/project/mapping.yml +45 -0
  129. cumulusci/files/templates/project/scratch_def.json +25 -0
  130. cumulusci/oauth/__init__.py +0 -0
  131. cumulusci/oauth/client.py +400 -0
  132. cumulusci/oauth/exceptions.py +9 -0
  133. cumulusci/oauth/salesforce.py +95 -0
  134. cumulusci/oauth/tests/__init__.py +0 -0
  135. cumulusci/oauth/tests/cassettes/test_get_device_code.yaml +22 -0
  136. cumulusci/oauth/tests/cassettes/test_get_device_oauth_token.yaml +74 -0
  137. cumulusci/oauth/tests/test_client.py +308 -0
  138. cumulusci/oauth/tests/test_salesforce.py +46 -0
  139. cumulusci/plugins/__init__.py +3 -0
  140. cumulusci/plugins/plugin_base.py +93 -0
  141. cumulusci/plugins/plugin_loader.py +59 -0
  142. cumulusci/robotframework/CumulusCI.py +340 -0
  143. cumulusci/robotframework/CumulusCI.robot +7 -0
  144. cumulusci/robotframework/Performance.py +165 -0
  145. cumulusci/robotframework/Salesforce.py +936 -0
  146. cumulusci/robotframework/Salesforce.robot +192 -0
  147. cumulusci/robotframework/SalesforceAPI.py +416 -0
  148. cumulusci/robotframework/SalesforcePlaywright.py +220 -0
  149. cumulusci/robotframework/SalesforcePlaywright.robot +40 -0
  150. cumulusci/robotframework/__init__.py +2 -0
  151. cumulusci/robotframework/base_library.py +39 -0
  152. cumulusci/robotframework/faker_mixin.py +89 -0
  153. cumulusci/robotframework/form_handlers.py +222 -0
  154. cumulusci/robotframework/javascript/cci_init.js +34 -0
  155. cumulusci/robotframework/javascript/cumulusci.js +4 -0
  156. cumulusci/robotframework/locator_manager.py +197 -0
  157. cumulusci/robotframework/locators_56.py +88 -0
  158. cumulusci/robotframework/locators_57.py +5 -0
  159. cumulusci/robotframework/pageobjects/BasePageObjects.py +433 -0
  160. cumulusci/robotframework/pageobjects/ObjectManagerPageObject.py +246 -0
  161. cumulusci/robotframework/pageobjects/PageObjectLibrary.py +45 -0
  162. cumulusci/robotframework/pageobjects/PageObjects.py +351 -0
  163. cumulusci/robotframework/pageobjects/__init__.py +12 -0
  164. cumulusci/robotframework/pageobjects/baseobjects.py +120 -0
  165. cumulusci/robotframework/perftests/short/collection_perf.robot +105 -0
  166. cumulusci/robotframework/tests/CustomObjectTestPage.py +10 -0
  167. cumulusci/robotframework/tests/FooTestPage.py +8 -0
  168. cumulusci/robotframework/tests/cumulusci/base.robot +40 -0
  169. cumulusci/robotframework/tests/cumulusci/bulkdata.robot +38 -0
  170. cumulusci/robotframework/tests/cumulusci/communities.robot +57 -0
  171. cumulusci/robotframework/tests/cumulusci/datagen.robot +84 -0
  172. cumulusci/robotframework/tests/salesforce/TestLibraryA.py +24 -0
  173. cumulusci/robotframework/tests/salesforce/TestLibraryB.py +20 -0
  174. cumulusci/robotframework/tests/salesforce/TestListener.py +93 -0
  175. cumulusci/robotframework/tests/salesforce/api.robot +178 -0
  176. cumulusci/robotframework/tests/salesforce/browsers.robot +143 -0
  177. cumulusci/robotframework/tests/salesforce/classic.robot +51 -0
  178. cumulusci/robotframework/tests/salesforce/create_contact.robot +59 -0
  179. cumulusci/robotframework/tests/salesforce/faker.robot +68 -0
  180. cumulusci/robotframework/tests/salesforce/forms.robot +172 -0
  181. cumulusci/robotframework/tests/salesforce/label_locator.robot +244 -0
  182. cumulusci/robotframework/tests/salesforce/labels.html +33 -0
  183. cumulusci/robotframework/tests/salesforce/locators.robot +149 -0
  184. cumulusci/robotframework/tests/salesforce/pageobjects/base_pageobjects.robot +100 -0
  185. cumulusci/robotframework/tests/salesforce/pageobjects/example_page_object.py +25 -0
  186. cumulusci/robotframework/tests/salesforce/pageobjects/listing_page.robot +115 -0
  187. cumulusci/robotframework/tests/salesforce/pageobjects/objectmanager.robot +74 -0
  188. cumulusci/robotframework/tests/salesforce/pageobjects/pageobjects.robot +171 -0
  189. cumulusci/robotframework/tests/salesforce/performance.robot +109 -0
  190. cumulusci/robotframework/tests/salesforce/playwright/javascript_keywords.robot +33 -0
  191. cumulusci/robotframework/tests/salesforce/playwright/open_test_browser.robot +48 -0
  192. cumulusci/robotframework/tests/salesforce/playwright/playwright.robot +24 -0
  193. cumulusci/robotframework/tests/salesforce/playwright/ui.robot +32 -0
  194. cumulusci/robotframework/tests/salesforce/populate.robot +89 -0
  195. cumulusci/robotframework/tests/salesforce/test_testlistener.py +37 -0
  196. cumulusci/robotframework/tests/salesforce/ui.robot +361 -0
  197. cumulusci/robotframework/tests/test_cumulusci_library.py +304 -0
  198. cumulusci/robotframework/tests/test_locator_manager.py +158 -0
  199. cumulusci/robotframework/tests/test_pageobjects.py +291 -0
  200. cumulusci/robotframework/tests/test_performance.py +38 -0
  201. cumulusci/robotframework/tests/test_salesforce.py +79 -0
  202. cumulusci/robotframework/tests/test_salesforce_locators.py +73 -0
  203. cumulusci/robotframework/tests/test_template_util.py +53 -0
  204. cumulusci/robotframework/tests/test_utils.py +106 -0
  205. cumulusci/robotframework/utils.py +283 -0
  206. cumulusci/salesforce_api/__init__.py +0 -0
  207. cumulusci/salesforce_api/exceptions.py +23 -0
  208. cumulusci/salesforce_api/filterable_objects.py +96 -0
  209. cumulusci/salesforce_api/mc_soap_envelopes.py +89 -0
  210. cumulusci/salesforce_api/metadata.py +721 -0
  211. cumulusci/salesforce_api/org_schema.py +571 -0
  212. cumulusci/salesforce_api/org_schema_models.py +226 -0
  213. cumulusci/salesforce_api/package_install.py +265 -0
  214. cumulusci/salesforce_api/package_zip.py +301 -0
  215. cumulusci/salesforce_api/rest_deploy.py +148 -0
  216. cumulusci/salesforce_api/retrieve_profile_api.py +301 -0
  217. cumulusci/salesforce_api/soap_envelopes.py +177 -0
  218. cumulusci/salesforce_api/tests/__init__.py +0 -0
  219. cumulusci/salesforce_api/tests/metadata_test_strings.py +24 -0
  220. cumulusci/salesforce_api/tests/test_metadata.py +1015 -0
  221. cumulusci/salesforce_api/tests/test_package_install.py +219 -0
  222. cumulusci/salesforce_api/tests/test_package_zip.py +380 -0
  223. cumulusci/salesforce_api/tests/test_rest_deploy.py +264 -0
  224. cumulusci/salesforce_api/tests/test_retrieve_profile_api.py +337 -0
  225. cumulusci/salesforce_api/tests/test_utils.py +124 -0
  226. cumulusci/salesforce_api/utils.py +51 -0
  227. cumulusci/schema/cumulusci.jsonschema.json +782 -0
  228. cumulusci/tasks/__init__.py +0 -0
  229. cumulusci/tasks/apex/__init__.py +0 -0
  230. cumulusci/tasks/apex/anon.py +157 -0
  231. cumulusci/tasks/apex/batch.py +180 -0
  232. cumulusci/tasks/apex/testrunner.py +835 -0
  233. cumulusci/tasks/apex/tests/cassettes/ManualEditTestApexIntegrationTests.test_run_tests__integration_test.yaml +703 -0
  234. cumulusci/tasks/apex/tests/test_apex_tasks.py +1558 -0
  235. cumulusci/tasks/base_source_control_task.py +17 -0
  236. cumulusci/tasks/bulkdata/__init__.py +15 -0
  237. cumulusci/tasks/bulkdata/base_generate_data_task.py +96 -0
  238. cumulusci/tasks/bulkdata/dates.py +97 -0
  239. cumulusci/tasks/bulkdata/delete.py +156 -0
  240. cumulusci/tasks/bulkdata/extract.py +441 -0
  241. cumulusci/tasks/bulkdata/extract_dataset_utils/calculate_dependencies.py +117 -0
  242. cumulusci/tasks/bulkdata/extract_dataset_utils/extract_yml.py +123 -0
  243. cumulusci/tasks/bulkdata/extract_dataset_utils/hardcoded_default_declarations.py +49 -0
  244. cumulusci/tasks/bulkdata/extract_dataset_utils/synthesize_extract_declarations.py +283 -0
  245. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_extract_yml.py +142 -0
  246. cumulusci/tasks/bulkdata/extract_dataset_utils/tests/test_synthesize_extract_declarations.py +575 -0
  247. cumulusci/tasks/bulkdata/factory_utils.py +134 -0
  248. cumulusci/tasks/bulkdata/generate.py +4 -0
  249. cumulusci/tasks/bulkdata/generate_and_load_data.py +232 -0
  250. cumulusci/tasks/bulkdata/generate_and_load_data_from_yaml.py +19 -0
  251. cumulusci/tasks/bulkdata/generate_from_yaml.py +183 -0
  252. cumulusci/tasks/bulkdata/generate_mapping.py +434 -0
  253. cumulusci/tasks/bulkdata/generate_mapping_utils/dependency_map.py +169 -0
  254. cumulusci/tasks/bulkdata/generate_mapping_utils/extract_mapping_file_generator.py +45 -0
  255. cumulusci/tasks/bulkdata/generate_mapping_utils/generate_mapping_from_declarations.py +121 -0
  256. cumulusci/tasks/bulkdata/generate_mapping_utils/load_mapping_file_generator.py +127 -0
  257. cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_generator_post_processes.py +53 -0
  258. cumulusci/tasks/bulkdata/generate_mapping_utils/mapping_transforms.py +139 -0
  259. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_extract_mapping_from_declarations.py +135 -0
  260. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_generate_load_mapping_from_declarations.py +330 -0
  261. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_generator_post_processes.py +60 -0
  262. cumulusci/tasks/bulkdata/generate_mapping_utils/tests/test_mapping_transforms.py +188 -0
  263. cumulusci/tasks/bulkdata/load.py +1196 -0
  264. cumulusci/tasks/bulkdata/mapping_parser.py +811 -0
  265. cumulusci/tasks/bulkdata/query_transformers.py +264 -0
  266. cumulusci/tasks/bulkdata/select_utils.py +792 -0
  267. cumulusci/tasks/bulkdata/snowfakery.py +753 -0
  268. cumulusci/tasks/bulkdata/snowfakery_utils/queue_manager.py +478 -0
  269. cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_run_until.py +141 -0
  270. cumulusci/tasks/bulkdata/snowfakery_utils/snowfakery_working_directory.py +53 -0
  271. cumulusci/tasks/bulkdata/snowfakery_utils/subtask_configurator.py +64 -0
  272. cumulusci/tasks/bulkdata/step.py +1242 -0
  273. cumulusci/tasks/bulkdata/tests/__init__.py +0 -0
  274. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_random_strategy.yaml +147 -0
  275. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_annoy_strategy.yaml +123 -0
  276. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy.yaml +313 -0
  277. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_select_and_insert_strategy_bulk.yaml +550 -0
  278. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_similarity_strategy.yaml +175 -0
  279. cumulusci/tasks/bulkdata/tests/cassettes/TestSelect.test_select_standard_strategy.yaml +147 -0
  280. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__multiple_needed.yaml +69 -0
  281. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__none_needed.yaml +22 -0
  282. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_run_until_records_in_org__one_needed.yaml +24 -0
  283. cumulusci/tasks/bulkdata/tests/cassettes/TestSnowfakery.test_snowfakery_query_salesforce.yaml +25 -0
  284. cumulusci/tasks/bulkdata/tests/cassettes/TestUpdatesIntegrationTests.test_updates_task.yaml +80 -0
  285. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_simple_upsert__rest.yaml +270 -0
  286. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert__rest.yaml +267 -0
  287. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field__rest.yaml +369 -0
  288. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_external_id_field_rest__duplicate_error.yaml +204 -0
  289. cumulusci/tasks/bulkdata/tests/cassettes/TestUpsert.test_upsert_complex_fields__bulk.yaml +675 -0
  290. cumulusci/tasks/bulkdata/tests/dummy_data_factory.py +36 -0
  291. cumulusci/tasks/bulkdata/tests/integration_test_utils.py +49 -0
  292. cumulusci/tasks/bulkdata/tests/mapping-oid.yml +87 -0
  293. cumulusci/tasks/bulkdata/tests/mapping_after.yml +38 -0
  294. cumulusci/tasks/bulkdata/tests/mapping_poly.yml +34 -0
  295. cumulusci/tasks/bulkdata/tests/mapping_poly_incomplete.yml +20 -0
  296. cumulusci/tasks/bulkdata/tests/mapping_poly_wrong.yml +21 -0
  297. cumulusci/tasks/bulkdata/tests/mapping_select.yml +20 -0
  298. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_strategy.yml +20 -0
  299. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_number.yml +21 -0
  300. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__invalid_strategy.yml +21 -0
  301. cumulusci/tasks/bulkdata/tests/mapping_select_invalid_threshold__non_float.yml +21 -0
  302. cumulusci/tasks/bulkdata/tests/mapping_select_missing_priority_fields.yml +22 -0
  303. cumulusci/tasks/bulkdata/tests/mapping_select_no_priority_fields.yml +18 -0
  304. cumulusci/tasks/bulkdata/tests/mapping_simple.yml +27 -0
  305. cumulusci/tasks/bulkdata/tests/mapping_v1.yml +28 -0
  306. cumulusci/tasks/bulkdata/tests/mapping_v2.yml +21 -0
  307. cumulusci/tasks/bulkdata/tests/mapping_v3.yml +32 -0
  308. cumulusci/tasks/bulkdata/tests/mapping_vanilla_sf.yml +69 -0
  309. cumulusci/tasks/bulkdata/tests/mock_data_factory_without_mapping.py +12 -0
  310. cumulusci/tasks/bulkdata/tests/person_accounts.yml +23 -0
  311. cumulusci/tasks/bulkdata/tests/person_accounts_minimal.yml +15 -0
  312. cumulusci/tasks/bulkdata/tests/recordtypes.yml +8 -0
  313. cumulusci/tasks/bulkdata/tests/recordtypes_2.yml +6 -0
  314. cumulusci/tasks/bulkdata/tests/recordtypes_with_ispersontype.yml +8 -0
  315. cumulusci/tasks/bulkdata/tests/snowfakery/child/child2.yml +3 -0
  316. cumulusci/tasks/bulkdata/tests/snowfakery/child.yml +4 -0
  317. cumulusci/tasks/bulkdata/tests/snowfakery/gen_npsp_standard_objects.recipe.yml +89 -0
  318. cumulusci/tasks/bulkdata/tests/snowfakery/include_parent.yml +3 -0
  319. cumulusci/tasks/bulkdata/tests/snowfakery/npsp_standard_objects_macros.yml +34 -0
  320. cumulusci/tasks/bulkdata/tests/snowfakery/options.recipe.yml +6 -0
  321. cumulusci/tasks/bulkdata/tests/snowfakery/query_snowfakery.recipe.yml +16 -0
  322. cumulusci/tasks/bulkdata/tests/snowfakery/sf_standard_object_macros.yml +83 -0
  323. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.load.yml +2 -0
  324. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery.recipe.yml +13 -0
  325. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_2.load.yml +5 -0
  326. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.load.yml +13 -0
  327. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels.recipe.yml +12 -0
  328. cumulusci/tasks/bulkdata/tests/snowfakery/simple_snowfakery_channels_2.load.yml +13 -0
  329. cumulusci/tasks/bulkdata/tests/snowfakery/unique_values.recipe.yml +4 -0
  330. cumulusci/tasks/bulkdata/tests/snowfakery/upsert.recipe.yml +23 -0
  331. cumulusci/tasks/bulkdata/tests/snowfakery/upsert_2.recipe.yml +29 -0
  332. cumulusci/tasks/bulkdata/tests/snowfakery/upsert_before.yml +10 -0
  333. cumulusci/tasks/bulkdata/tests/test_base_generate_data_tasks.py +61 -0
  334. cumulusci/tasks/bulkdata/tests/test_dates.py +99 -0
  335. cumulusci/tasks/bulkdata/tests/test_delete.py +404 -0
  336. cumulusci/tasks/bulkdata/tests/test_extract.py +1311 -0
  337. cumulusci/tasks/bulkdata/tests/test_factory_utils.py +55 -0
  338. cumulusci/tasks/bulkdata/tests/test_generate_and_load.py +252 -0
  339. cumulusci/tasks/bulkdata/tests/test_generate_from_snowfakery_task.py +343 -0
  340. cumulusci/tasks/bulkdata/tests/test_generatemapping.py +1039 -0
  341. cumulusci/tasks/bulkdata/tests/test_load.py +3175 -0
  342. cumulusci/tasks/bulkdata/tests/test_mapping_parser.py +1658 -0
  343. cumulusci/tasks/bulkdata/tests/test_query_db__joins_self_lookups.yml +12 -0
  344. cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups.yml +26 -0
  345. cumulusci/tasks/bulkdata/tests/test_query_db_joins_lookups_select.yml +48 -0
  346. cumulusci/tasks/bulkdata/tests/test_select.py +171 -0
  347. cumulusci/tasks/bulkdata/tests/test_select_utils.py +1057 -0
  348. cumulusci/tasks/bulkdata/tests/test_snowfakery.py +1153 -0
  349. cumulusci/tasks/bulkdata/tests/test_step.py +3957 -0
  350. cumulusci/tasks/bulkdata/tests/test_updates.py +513 -0
  351. cumulusci/tasks/bulkdata/tests/test_upsert.py +1015 -0
  352. cumulusci/tasks/bulkdata/tests/test_utils.py +158 -0
  353. cumulusci/tasks/bulkdata/tests/testdata.db +0 -0
  354. cumulusci/tasks/bulkdata/tests/update_describe.py +50 -0
  355. cumulusci/tasks/bulkdata/tests/update_person_accounts.yml +23 -0
  356. cumulusci/tasks/bulkdata/tests/utils.py +114 -0
  357. cumulusci/tasks/bulkdata/update_data.py +260 -0
  358. cumulusci/tasks/bulkdata/upsert_utils.py +130 -0
  359. cumulusci/tasks/bulkdata/utils.py +249 -0
  360. cumulusci/tasks/command.py +178 -0
  361. cumulusci/tasks/connectedapp.py +186 -0
  362. cumulusci/tasks/create_package_version.py +778 -0
  363. cumulusci/tasks/datadictionary.py +745 -0
  364. cumulusci/tasks/dx_convert_from.py +26 -0
  365. cumulusci/tasks/github/__init__.py +17 -0
  366. cumulusci/tasks/github/base.py +16 -0
  367. cumulusci/tasks/github/commit_status.py +13 -0
  368. cumulusci/tasks/github/merge.py +11 -0
  369. cumulusci/tasks/github/publish.py +11 -0
  370. cumulusci/tasks/github/pull_request.py +11 -0
  371. cumulusci/tasks/github/release.py +11 -0
  372. cumulusci/tasks/github/release_report.py +11 -0
  373. cumulusci/tasks/github/tag.py +11 -0
  374. cumulusci/tasks/github/tests/__init__.py +0 -0
  375. cumulusci/tasks/github/tests/test_util.py +202 -0
  376. cumulusci/tasks/github/tests/test_vcs_migration.py +44 -0
  377. cumulusci/tasks/github/tests/util_github_api.py +666 -0
  378. cumulusci/tasks/github/util.py +252 -0
  379. cumulusci/tasks/marketing_cloud/__init__.py +0 -0
  380. cumulusci/tasks/marketing_cloud/api.py +188 -0
  381. cumulusci/tasks/marketing_cloud/base.py +38 -0
  382. cumulusci/tasks/marketing_cloud/deploy.py +345 -0
  383. cumulusci/tasks/marketing_cloud/get_user_info.py +40 -0
  384. cumulusci/tasks/marketing_cloud/mc_constants.py +1 -0
  385. cumulusci/tasks/marketing_cloud/tests/__init__.py +0 -0
  386. cumulusci/tasks/marketing_cloud/tests/conftest.py +46 -0
  387. cumulusci/tasks/marketing_cloud/tests/expected-payload.json +110 -0
  388. cumulusci/tasks/marketing_cloud/tests/test_api.py +97 -0
  389. cumulusci/tasks/marketing_cloud/tests/test_api_soap_envelopes.py +145 -0
  390. cumulusci/tasks/marketing_cloud/tests/test_base.py +14 -0
  391. cumulusci/tasks/marketing_cloud/tests/test_deploy.py +400 -0
  392. cumulusci/tasks/marketing_cloud/tests/test_get_user_info.py +141 -0
  393. cumulusci/tasks/marketing_cloud/tests/validation-response.json +39 -0
  394. cumulusci/tasks/metadata/__init__.py +0 -0
  395. cumulusci/tasks/metadata/ee_src.py +94 -0
  396. cumulusci/tasks/metadata/managed_src.py +100 -0
  397. cumulusci/tasks/metadata/metadata_map.yml +868 -0
  398. cumulusci/tasks/metadata/modify.py +99 -0
  399. cumulusci/tasks/metadata/package.py +684 -0
  400. cumulusci/tasks/metadata/tests/__init__.py +0 -0
  401. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/.hidden/.keep +0 -0
  402. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/destructiveChanges.xml +9 -0
  403. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package.xml +9 -0
  404. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/package_install_uninstall.xml +11 -0
  405. cumulusci/tasks/metadata/tests/package_metadata/namespaced_report_folder/reports/namespace__TestFolder/TestReport.report +3 -0
  406. cumulusci/tasks/metadata/tests/sample_package.xml +9 -0
  407. cumulusci/tasks/metadata/tests/test_ee_src.py +112 -0
  408. cumulusci/tasks/metadata/tests/test_managed_src.py +111 -0
  409. cumulusci/tasks/metadata/tests/test_modify.py +123 -0
  410. cumulusci/tasks/metadata/tests/test_package.py +476 -0
  411. cumulusci/tasks/metadata_etl/__init__.py +29 -0
  412. cumulusci/tasks/metadata_etl/base.py +436 -0
  413. cumulusci/tasks/metadata_etl/duplicate_rules.py +24 -0
  414. cumulusci/tasks/metadata_etl/field_sets.py +70 -0
  415. cumulusci/tasks/metadata_etl/help_text.py +92 -0
  416. cumulusci/tasks/metadata_etl/layouts.py +550 -0
  417. cumulusci/tasks/metadata_etl/objects.py +68 -0
  418. cumulusci/tasks/metadata_etl/permissions.py +167 -0
  419. cumulusci/tasks/metadata_etl/picklists.py +221 -0
  420. cumulusci/tasks/metadata_etl/remote_site_settings.py +99 -0
  421. cumulusci/tasks/metadata_etl/sharing.py +138 -0
  422. cumulusci/tasks/metadata_etl/tests/test_base.py +512 -0
  423. cumulusci/tasks/metadata_etl/tests/test_duplicate_rules.py +22 -0
  424. cumulusci/tasks/metadata_etl/tests/test_field_sets.py +156 -0
  425. cumulusci/tasks/metadata_etl/tests/test_help_text.py +387 -0
  426. cumulusci/tasks/metadata_etl/tests/test_ip_ranges.py +85 -0
  427. cumulusci/tasks/metadata_etl/tests/test_layouts.py +858 -0
  428. cumulusci/tasks/metadata_etl/tests/test_objects.py +236 -0
  429. cumulusci/tasks/metadata_etl/tests/test_permissions.py +223 -0
  430. cumulusci/tasks/metadata_etl/tests/test_picklists.py +547 -0
  431. cumulusci/tasks/metadata_etl/tests/test_remote_site_settings.py +46 -0
  432. cumulusci/tasks/metadata_etl/tests/test_sharing.py +333 -0
  433. cumulusci/tasks/metadata_etl/tests/test_value_sets.py +298 -0
  434. cumulusci/tasks/metadata_etl/value_sets.py +106 -0
  435. cumulusci/tasks/metadeploy.py +393 -0
  436. cumulusci/tasks/metaxml.py +88 -0
  437. cumulusci/tasks/preflight/__init__.py +0 -0
  438. cumulusci/tasks/preflight/dataset_load.py +49 -0
  439. cumulusci/tasks/preflight/licenses.py +86 -0
  440. cumulusci/tasks/preflight/packages.py +14 -0
  441. cumulusci/tasks/preflight/permsets.py +23 -0
  442. cumulusci/tasks/preflight/recordtypes.py +16 -0
  443. cumulusci/tasks/preflight/retrieve_tasks.py +30 -0
  444. cumulusci/tasks/preflight/settings.py +77 -0
  445. cumulusci/tasks/preflight/sobjects.py +202 -0
  446. cumulusci/tasks/preflight/tests/test_dataset_load.py +85 -0
  447. cumulusci/tasks/preflight/tests/test_licenses.py +174 -0
  448. cumulusci/tasks/preflight/tests/test_packages.py +14 -0
  449. cumulusci/tasks/preflight/tests/test_permset_preflights.py +51 -0
  450. cumulusci/tasks/preflight/tests/test_recordtypes.py +30 -0
  451. cumulusci/tasks/preflight/tests/test_retrieve_tasks.py +62 -0
  452. cumulusci/tasks/preflight/tests/test_settings.py +130 -0
  453. cumulusci/tasks/preflight/tests/test_sobjects.py +231 -0
  454. cumulusci/tasks/push/README.md +59 -0
  455. cumulusci/tasks/push/__init__.py +0 -0
  456. cumulusci/tasks/push/push_api.py +659 -0
  457. cumulusci/tasks/push/pushfails.py +136 -0
  458. cumulusci/tasks/push/tasks.py +476 -0
  459. cumulusci/tasks/push/tests/conftest.py +263 -0
  460. cumulusci/tasks/push/tests/test_push_api.py +951 -0
  461. cumulusci/tasks/push/tests/test_push_tasks.py +659 -0
  462. cumulusci/tasks/release_notes/README.md +63 -0
  463. cumulusci/tasks/release_notes/__init__.py +0 -0
  464. cumulusci/tasks/release_notes/exceptions.py +5 -0
  465. cumulusci/tasks/release_notes/generator.py +137 -0
  466. cumulusci/tasks/release_notes/parser.py +232 -0
  467. cumulusci/tasks/release_notes/provider.py +44 -0
  468. cumulusci/tasks/release_notes/task.py +300 -0
  469. cumulusci/tasks/release_notes/tests/__init__.py +0 -0
  470. cumulusci/tasks/release_notes/tests/change_notes/full/example1.md +17 -0
  471. cumulusci/tasks/release_notes/tests/change_notes/multi/1.txt +1 -0
  472. cumulusci/tasks/release_notes/tests/change_notes/multi/2.txt +1 -0
  473. cumulusci/tasks/release_notes/tests/change_notes/multi/3.txt +1 -0
  474. cumulusci/tasks/release_notes/tests/change_notes/single/1.txt +1 -0
  475. cumulusci/tasks/release_notes/tests/test_generator.py +582 -0
  476. cumulusci/tasks/release_notes/tests/test_parser.py +867 -0
  477. cumulusci/tasks/release_notes/tests/test_provider.py +512 -0
  478. cumulusci/tasks/release_notes/tests/test_task.py +461 -0
  479. cumulusci/tasks/release_notes/tests/utils.py +153 -0
  480. cumulusci/tasks/robotframework/__init__.py +3 -0
  481. cumulusci/tasks/robotframework/debugger/DebugListener.py +100 -0
  482. cumulusci/tasks/robotframework/debugger/__init__.py +10 -0
  483. cumulusci/tasks/robotframework/debugger/model.py +87 -0
  484. cumulusci/tasks/robotframework/debugger/ui.py +259 -0
  485. cumulusci/tasks/robotframework/libdoc.py +269 -0
  486. cumulusci/tasks/robotframework/robotframework.py +392 -0
  487. cumulusci/tasks/robotframework/stylesheet.css +130 -0
  488. cumulusci/tasks/robotframework/template.html +109 -0
  489. cumulusci/tasks/robotframework/tests/TestLibrary.py +18 -0
  490. cumulusci/tasks/robotframework/tests/TestPageObjects.py +31 -0
  491. cumulusci/tasks/robotframework/tests/TestResource.robot +8 -0
  492. cumulusci/tasks/robotframework/tests/failing_tests.robot +16 -0
  493. cumulusci/tasks/robotframework/tests/performance.robot +23 -0
  494. cumulusci/tasks/robotframework/tests/test_browser_proxies.py +137 -0
  495. cumulusci/tasks/robotframework/tests/test_debugger.py +360 -0
  496. cumulusci/tasks/robotframework/tests/test_robot_parallel.py +141 -0
  497. cumulusci/tasks/robotframework/tests/test_robotframework.py +860 -0
  498. cumulusci/tasks/salesforce/BaseRetrieveMetadata.py +58 -0
  499. cumulusci/tasks/salesforce/BaseSalesforceApiTask.py +45 -0
  500. cumulusci/tasks/salesforce/BaseSalesforceMetadataApiTask.py +18 -0
  501. cumulusci/tasks/salesforce/BaseSalesforceTask.py +4 -0
  502. cumulusci/tasks/salesforce/BaseUninstallMetadata.py +41 -0
  503. cumulusci/tasks/salesforce/CreateCommunity.py +124 -0
  504. cumulusci/tasks/salesforce/CreatePackage.py +29 -0
  505. cumulusci/tasks/salesforce/Deploy.py +240 -0
  506. cumulusci/tasks/salesforce/DeployBundles.py +88 -0
  507. cumulusci/tasks/salesforce/DescribeMetadataTypes.py +26 -0
  508. cumulusci/tasks/salesforce/EnsureRecordTypes.py +202 -0
  509. cumulusci/tasks/salesforce/GetInstalledPackages.py +8 -0
  510. cumulusci/tasks/salesforce/ListCommunities.py +40 -0
  511. cumulusci/tasks/salesforce/ListCommunityTemplates.py +19 -0
  512. cumulusci/tasks/salesforce/PublishCommunity.py +62 -0
  513. cumulusci/tasks/salesforce/RetrievePackaged.py +41 -0
  514. cumulusci/tasks/salesforce/RetrieveReportsAndDashboards.py +82 -0
  515. cumulusci/tasks/salesforce/RetrieveUnpackaged.py +36 -0
  516. cumulusci/tasks/salesforce/SOQLQuery.py +39 -0
  517. cumulusci/tasks/salesforce/UninstallLocal.py +15 -0
  518. cumulusci/tasks/salesforce/UninstallLocalBundles.py +28 -0
  519. cumulusci/tasks/salesforce/UninstallLocalNamespacedBundles.py +58 -0
  520. cumulusci/tasks/salesforce/UninstallPackage.py +32 -0
  521. cumulusci/tasks/salesforce/UninstallPackaged.py +56 -0
  522. cumulusci/tasks/salesforce/UpdateAdminProfile.py +8 -0
  523. cumulusci/tasks/salesforce/__init__.py +79 -0
  524. cumulusci/tasks/salesforce/activate_flow.py +74 -0
  525. cumulusci/tasks/salesforce/check_components.py +324 -0
  526. cumulusci/tasks/salesforce/composite.py +142 -0
  527. cumulusci/tasks/salesforce/create_permission_sets.py +35 -0
  528. cumulusci/tasks/salesforce/custom_settings.py +134 -0
  529. cumulusci/tasks/salesforce/custom_settings_wait.py +132 -0
  530. cumulusci/tasks/salesforce/enable_prediction.py +107 -0
  531. cumulusci/tasks/salesforce/insert_record.py +40 -0
  532. cumulusci/tasks/salesforce/install_package_version.py +242 -0
  533. cumulusci/tasks/salesforce/license_preflights.py +8 -0
  534. cumulusci/tasks/salesforce/network_member_group.py +178 -0
  535. cumulusci/tasks/salesforce/nonsourcetracking.py +228 -0
  536. cumulusci/tasks/salesforce/org_settings.py +193 -0
  537. cumulusci/tasks/salesforce/package_upload.py +328 -0
  538. cumulusci/tasks/salesforce/profiles.py +74 -0
  539. cumulusci/tasks/salesforce/promote_package_version.py +376 -0
  540. cumulusci/tasks/salesforce/retrieve_profile.py +195 -0
  541. cumulusci/tasks/salesforce/salesforce_files.py +244 -0
  542. cumulusci/tasks/salesforce/sourcetracking.py +507 -0
  543. cumulusci/tasks/salesforce/tests/__init__.py +3 -0
  544. cumulusci/tasks/salesforce/tests/test_CreateCommunity.py +278 -0
  545. cumulusci/tasks/salesforce/tests/test_CreatePackage.py +22 -0
  546. cumulusci/tasks/salesforce/tests/test_Deploy.py +470 -0
  547. cumulusci/tasks/salesforce/tests/test_DeployBundles.py +76 -0
  548. cumulusci/tasks/salesforce/tests/test_EnsureRecordTypes.py +345 -0
  549. cumulusci/tasks/salesforce/tests/test_ListCommunities.py +84 -0
  550. cumulusci/tasks/salesforce/tests/test_ListCommunityTemplates.py +49 -0
  551. cumulusci/tasks/salesforce/tests/test_PackageUpload.py +547 -0
  552. cumulusci/tasks/salesforce/tests/test_ProfileGrantAllAccess.py +699 -0
  553. cumulusci/tasks/salesforce/tests/test_PublishCommunity.py +181 -0
  554. cumulusci/tasks/salesforce/tests/test_RetrievePackaged.py +24 -0
  555. cumulusci/tasks/salesforce/tests/test_RetrieveReportsAndDashboards.py +56 -0
  556. cumulusci/tasks/salesforce/tests/test_RetrieveUnpackaged.py +21 -0
  557. cumulusci/tasks/salesforce/tests/test_SOQLQuery.py +30 -0
  558. cumulusci/tasks/salesforce/tests/test_UninstallLocal.py +15 -0
  559. cumulusci/tasks/salesforce/tests/test_UninstallLocalBundles.py +19 -0
  560. cumulusci/tasks/salesforce/tests/test_UninstallLocalNamespacedBundles.py +22 -0
  561. cumulusci/tasks/salesforce/tests/test_UninstallPackage.py +19 -0
  562. cumulusci/tasks/salesforce/tests/test_UninstallPackaged.py +66 -0
  563. cumulusci/tasks/salesforce/tests/test_UninstallPackagedIncremental.py +127 -0
  564. cumulusci/tasks/salesforce/tests/test_activate_flow.py +132 -0
  565. cumulusci/tasks/salesforce/tests/test_base_tasks.py +110 -0
  566. cumulusci/tasks/salesforce/tests/test_check_components.py +445 -0
  567. cumulusci/tasks/salesforce/tests/test_composite.py +250 -0
  568. cumulusci/tasks/salesforce/tests/test_create_permission_sets.py +41 -0
  569. cumulusci/tasks/salesforce/tests/test_custom_settings.py +227 -0
  570. cumulusci/tasks/salesforce/tests/test_custom_settings_wait.py +174 -0
  571. cumulusci/tasks/salesforce/tests/test_describemetadatatypes.py +18 -0
  572. cumulusci/tasks/salesforce/tests/test_enable_prediction.py +240 -0
  573. cumulusci/tasks/salesforce/tests/test_insert_record.py +110 -0
  574. cumulusci/tasks/salesforce/tests/test_install_package_version.py +464 -0
  575. cumulusci/tasks/salesforce/tests/test_network_member_group.py +444 -0
  576. cumulusci/tasks/salesforce/tests/test_nonsourcetracking.py +235 -0
  577. cumulusci/tasks/salesforce/tests/test_org_settings.py +407 -0
  578. cumulusci/tasks/salesforce/tests/test_profiles.py +202 -0
  579. cumulusci/tasks/salesforce/tests/test_retrieve_profile.py +287 -0
  580. cumulusci/tasks/salesforce/tests/test_salesforce_files.py +228 -0
  581. cumulusci/tasks/salesforce/tests/test_sourcetracking.py +350 -0
  582. cumulusci/tasks/salesforce/tests/test_trigger_handlers.py +300 -0
  583. cumulusci/tasks/salesforce/tests/test_update_dependencies.py +509 -0
  584. cumulusci/tasks/salesforce/tests/util.py +79 -0
  585. cumulusci/tasks/salesforce/trigger_handlers.py +119 -0
  586. cumulusci/tasks/salesforce/uninstall_packaged_incremental.py +136 -0
  587. cumulusci/tasks/salesforce/update_dependencies.py +290 -0
  588. cumulusci/tasks/salesforce/update_profile.py +339 -0
  589. cumulusci/tasks/salesforce/users/permsets.py +227 -0
  590. cumulusci/tasks/salesforce/users/photos.py +162 -0
  591. cumulusci/tasks/salesforce/users/tests/photo.mock.txt +1 -0
  592. cumulusci/tasks/salesforce/users/tests/test_permsets.py +950 -0
  593. cumulusci/tasks/salesforce/users/tests/test_photos.py +373 -0
  594. cumulusci/tasks/sample_data/capture_sample_data.py +77 -0
  595. cumulusci/tasks/sample_data/load_sample_data.py +85 -0
  596. cumulusci/tasks/sample_data/test_capture_sample_data.py +117 -0
  597. cumulusci/tasks/sample_data/test_load_sample_data.py +121 -0
  598. cumulusci/tasks/sfdx.py +83 -0
  599. cumulusci/tasks/tests/__init__.py +1 -0
  600. cumulusci/tasks/tests/conftest.py +30 -0
  601. cumulusci/tasks/tests/test_command.py +129 -0
  602. cumulusci/tasks/tests/test_connectedapp.py +236 -0
  603. cumulusci/tasks/tests/test_create_package_version.py +847 -0
  604. cumulusci/tasks/tests/test_datadictionary.py +1575 -0
  605. cumulusci/tasks/tests/test_dx_convert_from.py +60 -0
  606. cumulusci/tasks/tests/test_metadeploy.py +624 -0
  607. cumulusci/tasks/tests/test_metaxml.py +99 -0
  608. cumulusci/tasks/tests/test_promote_package_version.py +488 -0
  609. cumulusci/tasks/tests/test_pushfails.py +96 -0
  610. cumulusci/tasks/tests/test_salesforce.py +72 -0
  611. cumulusci/tasks/tests/test_sfdx.py +105 -0
  612. cumulusci/tasks/tests/test_util.py +207 -0
  613. cumulusci/tasks/util.py +261 -0
  614. cumulusci/tasks/vcs/__init__.py +19 -0
  615. cumulusci/tasks/vcs/commit_status.py +58 -0
  616. cumulusci/tasks/vcs/create_commit_status.py +37 -0
  617. cumulusci/tasks/vcs/download_extract.py +199 -0
  618. cumulusci/tasks/vcs/merge.py +298 -0
  619. cumulusci/tasks/vcs/publish.py +207 -0
  620. cumulusci/tasks/vcs/pull_request.py +9 -0
  621. cumulusci/tasks/vcs/release.py +134 -0
  622. cumulusci/tasks/vcs/release_report.py +105 -0
  623. cumulusci/tasks/vcs/tag.py +31 -0
  624. cumulusci/tasks/vcs/tests/github/test_commit_status.py +196 -0
  625. cumulusci/tasks/vcs/tests/github/test_download_extract.py +896 -0
  626. cumulusci/tasks/vcs/tests/github/test_merge.py +1118 -0
  627. cumulusci/tasks/vcs/tests/github/test_publish.py +823 -0
  628. cumulusci/tasks/vcs/tests/github/test_pull_request.py +29 -0
  629. cumulusci/tasks/vcs/tests/github/test_release.py +390 -0
  630. cumulusci/tasks/vcs/tests/github/test_release_report.py +109 -0
  631. cumulusci/tasks/vcs/tests/github/test_tag.py +90 -0
  632. cumulusci/tasks/vlocity/exceptions.py +2 -0
  633. cumulusci/tasks/vlocity/tests/test_vlocity.py +283 -0
  634. cumulusci/tasks/vlocity/vlocity.py +342 -0
  635. cumulusci/tests/__init__.py +1 -0
  636. cumulusci/tests/cassettes/GET_sobjects_Account_PersonAccount_describe.yaml +18 -0
  637. cumulusci/tests/cassettes/TestIntegrationInfrastructure.test_integration_tests.yaml +19 -0
  638. cumulusci/tests/pytest_plugins/pytest_sf_orgconnect.py +307 -0
  639. cumulusci/tests/pytest_plugins/pytest_sf_vcr.py +275 -0
  640. cumulusci/tests/pytest_plugins/pytest_sf_vcr_serializer.py +160 -0
  641. cumulusci/tests/pytest_plugins/pytest_typeguard.py +5 -0
  642. cumulusci/tests/pytest_plugins/test_vcr_string_compressor.py +49 -0
  643. cumulusci/tests/pytest_plugins/vcr_string_compressor.py +97 -0
  644. cumulusci/tests/shared_cassettes/GET_sobjects_Account_describe.yaml +18 -0
  645. cumulusci/tests/shared_cassettes/GET_sobjects_Case_describe.yaml +18 -0
  646. cumulusci/tests/shared_cassettes/GET_sobjects_Contact_describe.yaml +4838 -0
  647. cumulusci/tests/shared_cassettes/GET_sobjects_Custom__c_describe.yaml +242 -0
  648. cumulusci/tests/shared_cassettes/GET_sobjects_Event_describe.yaml +19 -0
  649. cumulusci/tests/shared_cassettes/GET_sobjects_Global_describe.yaml +1338 -0
  650. cumulusci/tests/shared_cassettes/GET_sobjects_Lead_describe.yaml +18 -0
  651. cumulusci/tests/shared_cassettes/GET_sobjects_OpportunityContactRole_describe.yaml +34 -0
  652. cumulusci/tests/shared_cassettes/GET_sobjects_Opportunity_describe.yaml +1261 -0
  653. cumulusci/tests/shared_cassettes/GET_sobjects_Organization.yaml +49 -0
  654. cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfoList_xml.tpl +15 -0
  655. cumulusci/tests/shared_cassettes/vcr_string_templates/batchInfo_xml.tpl +13 -0
  656. cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_insert_xml.tpl +24 -0
  657. cumulusci/tests/shared_cassettes/vcr_string_templates/jobInfo_upsert_xml.tpl +25 -0
  658. cumulusci/tests/test_entry_points.py +20 -0
  659. cumulusci/tests/test_integration_infrastructure.py +131 -0
  660. cumulusci/tests/test_main.py +9 -0
  661. cumulusci/tests/test_schema.py +32 -0
  662. cumulusci/tests/test_utils.py +657 -0
  663. cumulusci/tests/test_vcr_serializer.py +134 -0
  664. cumulusci/tests/uncompressed_cassette.yaml +83 -0
  665. cumulusci/tests/util.py +344 -0
  666. cumulusci/utils/__init__.py +731 -0
  667. cumulusci/utils/classutils.py +9 -0
  668. cumulusci/utils/collections.py +32 -0
  669. cumulusci/utils/deprecation.py +11 -0
  670. cumulusci/utils/encryption.py +31 -0
  671. cumulusci/utils/fileutils.py +295 -0
  672. cumulusci/utils/git.py +142 -0
  673. cumulusci/utils/http/multi_request.py +214 -0
  674. cumulusci/utils/http/requests_utils.py +103 -0
  675. cumulusci/utils/http/tests/cassettes/ManualEditTestCompositeParallelSalesforce.test_http_headers.yaml +32 -0
  676. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_composite_parallel_salesforce.yaml +65 -0
  677. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_errors.yaml +24 -0
  678. cumulusci/utils/http/tests/cassettes/TestCompositeParallelSalesforce.test_reference_ids.yaml +49 -0
  679. cumulusci/utils/http/tests/test_multi_request.py +255 -0
  680. cumulusci/utils/iterators.py +21 -0
  681. cumulusci/utils/logging.py +128 -0
  682. cumulusci/utils/metaprogramming.py +10 -0
  683. cumulusci/utils/options.py +138 -0
  684. cumulusci/utils/parallel/queries_in_parallel/run_queries_in_parallel.py +29 -0
  685. cumulusci/utils/parallel/queries_in_parallel/tests/test_run_queries_in_parallel.py +50 -0
  686. cumulusci/utils/parallel/task_worker_queues/parallel_worker.py +238 -0
  687. cumulusci/utils/parallel/task_worker_queues/parallel_worker_queue.py +243 -0
  688. cumulusci/utils/parallel/task_worker_queues/tests/test_parallel_worker.py +353 -0
  689. cumulusci/utils/salesforce/count_sobjects.py +46 -0
  690. cumulusci/utils/salesforce/soql.py +17 -0
  691. cumulusci/utils/salesforce/tests/cassettes/ManualEdit_TestCountSObjects.test_count_sobjects__network_errors.yaml +23 -0
  692. cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects__errors.yaml +33 -0
  693. cumulusci/utils/salesforce/tests/cassettes/TestCountSObjects.test_count_sobjects_simple.yaml +29 -0
  694. cumulusci/utils/salesforce/tests/test_count_sobjects.py +29 -0
  695. cumulusci/utils/salesforce/tests/test_soql.py +30 -0
  696. cumulusci/utils/tests/cassettes/ManualEditTestDescribeOrg.test_minimal_schema.yaml +36 -0
  697. cumulusci/utils/tests/cassettes/ManualEdit_test_describe_to_sql.yaml +191 -0
  698. cumulusci/utils/tests/test_fileutils.py +284 -0
  699. cumulusci/utils/tests/test_git.py +85 -0
  700. cumulusci/utils/tests/test_logging.py +70 -0
  701. cumulusci/utils/tests/test_option_parsing.py +188 -0
  702. cumulusci/utils/tests/test_org_schema.py +691 -0
  703. cumulusci/utils/tests/test_org_schema_models.py +79 -0
  704. cumulusci/utils/tests/test_waiting.py +25 -0
  705. cumulusci/utils/version_strings.py +391 -0
  706. cumulusci/utils/waiting.py +42 -0
  707. cumulusci/utils/xml/__init__.py +91 -0
  708. cumulusci/utils/xml/metadata_tree.py +299 -0
  709. cumulusci/utils/xml/robot_xml.py +114 -0
  710. cumulusci/utils/xml/salesforce_encoding.py +100 -0
  711. cumulusci/utils/xml/test/test_metadata_tree.py +251 -0
  712. cumulusci/utils/xml/test/test_salesforce_encoding.py +173 -0
  713. cumulusci/utils/yaml/cumulusci_yml.py +401 -0
  714. cumulusci/utils/yaml/model_parser.py +156 -0
  715. cumulusci/utils/yaml/safer_loader.py +74 -0
  716. cumulusci/utils/yaml/tests/bad_cci.yml +5 -0
  717. cumulusci/utils/yaml/tests/cassettes/TestCumulusciYml.test_validate_url__with_errors.yaml +20 -0
  718. cumulusci/utils/yaml/tests/test_cumulusci_yml.py +286 -0
  719. cumulusci/utils/yaml/tests/test_model_parser.py +175 -0
  720. cumulusci/utils/yaml/tests/test_safer_loader.py +88 -0
  721. cumulusci/utils/ziputils.py +61 -0
  722. cumulusci/vcs/base.py +143 -0
  723. cumulusci/vcs/bootstrap.py +272 -0
  724. cumulusci/vcs/github/__init__.py +24 -0
  725. cumulusci/vcs/github/adapter.py +689 -0
  726. cumulusci/vcs/github/release_notes/generator.py +219 -0
  727. cumulusci/vcs/github/release_notes/parser.py +151 -0
  728. cumulusci/vcs/github/release_notes/provider.py +143 -0
  729. cumulusci/vcs/github/service.py +569 -0
  730. cumulusci/vcs/github/tests/test_adapter.py +138 -0
  731. cumulusci/vcs/github/tests/test_service.py +408 -0
  732. cumulusci/vcs/models.py +586 -0
  733. cumulusci/vcs/tests/conftest.py +41 -0
  734. cumulusci/vcs/tests/dummy_service.py +241 -0
  735. cumulusci/vcs/tests/test_vcs_base.py +687 -0
  736. cumulusci/vcs/tests/test_vcs_bootstrap.py +727 -0
  737. cumulusci/vcs/utils/__init__.py +31 -0
  738. cumulusci/vcs/vcs_source.py +287 -0
  739. cumulusci_plus-5.0.0.dist-info/METADATA +145 -0
  740. cumulusci_plus-5.0.0.dist-info/RECORD +744 -0
  741. cumulusci_plus-5.0.0.dist-info/WHEEL +4 -0
  742. cumulusci_plus-5.0.0.dist-info/entry_points.txt +3 -0
  743. cumulusci_plus-5.0.0.dist-info/licenses/AUTHORS.rst +41 -0
  744. cumulusci_plus-5.0.0.dist-info/licenses/LICENSE +30 -0
@@ -0,0 +1,1658 @@
1
+ import logging
2
+ from datetime import date
3
+ from io import StringIO
4
+ from pathlib import Path
5
+ from unittest import mock
6
+
7
+ import pytest
8
+ import responses
9
+
10
+ from cumulusci.core.exceptions import BulkDataException, YAMLParseException
11
+ from cumulusci.tasks.bulkdata.mapping_parser import (
12
+ CaseInsensitiveDict,
13
+ MappingLookup,
14
+ MappingStep,
15
+ ValidationError,
16
+ _infer_and_validate_lookups,
17
+ parse_from_yaml,
18
+ validate_and_inject_mapping,
19
+ )
20
+ from cumulusci.tasks.bulkdata.select_utils import SelectStrategy
21
+ from cumulusci.tasks.bulkdata.step import DataApi, DataOperationType
22
+ from cumulusci.tests.util import DummyOrgConfig, mock_describe_calls
23
+
24
+
25
+ class TestMappingParser:
26
+ def test_simple_parse(self):
27
+ base_path = Path(__file__).parent / "mapping_v2.yml"
28
+ assert parse_from_yaml(base_path)
29
+
30
+ def test_after(self):
31
+ base_path = Path(__file__).parent / "mapping_after.yml"
32
+ result = parse_from_yaml(base_path)
33
+
34
+ step = result["Insert Accounts"]
35
+ lookups = step["lookups"]
36
+ assert lookups
37
+ assert "after" in lookups["ParentId"]
38
+ after_list = {
39
+ lookup["after"] for lookup in lookups.values() if "after" in lookup
40
+ }
41
+ assert after_list
42
+
43
+ def test_deprecation(self, caplog):
44
+ base_path = Path(__file__).parent / "mapping_v2.yml"
45
+ caplog.set_level(logging.WARNING)
46
+
47
+ parse_from_yaml(base_path)
48
+ assert "record_type" in caplog.text
49
+
50
+ def test_deprecation_override(self, caplog):
51
+ base_path = Path(__file__).parent / "mapping_v2.yml"
52
+ caplog.set_level(logging.WARNING)
53
+ with mock.patch(
54
+ "cumulusci.tasks.bulkdata.mapping_parser.SHOULD_REPORT_RECORD_TYPE_DEPRECATION",
55
+ False,
56
+ ):
57
+ mapping = parse_from_yaml(base_path)
58
+ assert "record_type" not in caplog.text
59
+ assert mapping["Insert Households"]["record_type"] == "HH_Account"
60
+
61
+ def test_bad_mapping_syntax(self):
62
+ base_path = Path(__file__).parent / "mapping_v2.yml"
63
+ with open(base_path, "r") as f:
64
+ data = f.read().replace(":", ": abcd")
65
+ with pytest.raises(
66
+ YAMLParseException, match="An error occurred parsing yaml file .*"
67
+ ):
68
+ parse_from_yaml(StringIO(data))
69
+
70
+ def test_bad_mapping_grammar(self):
71
+ base_path = Path(__file__).parent / "mapping_v2.yml"
72
+ with open(base_path, "r") as f:
73
+ data = f.read().replace("record_type", "xyzzy")
74
+ with pytest.raises(ValidationError):
75
+ parse_from_yaml(StringIO(data))
76
+
77
+ def test_bad_mapping_id_mode(self):
78
+ base_path = Path(__file__).parent / "mapping_v2.yml"
79
+ with open(base_path, "r") as f:
80
+ data = f.read().replace("Name: name", "Id: sf_id")
81
+ with pytest.raises(ValidationError):
82
+ parse_from_yaml(StringIO(data))
83
+
84
+ def test_bad_mapping_oid_as_pk(self):
85
+ base_path = Path(__file__).parent / "mapping_v1.yml"
86
+ with open(base_path, "r") as f:
87
+ data = f.read().replace("api: bulk", "oid_as_pk: True`")
88
+ with pytest.raises(ValidationError):
89
+ parse_from_yaml(StringIO(data))
90
+
91
+ def test_bad_mapping_batch_size(self):
92
+ base_path = Path(__file__).parent / "mapping_v2.yml"
93
+ with open(base_path, "r") as f:
94
+ data = f.read().replace("record_type: HH_Account", "batch_size: 50000")
95
+ with pytest.raises(ValidationError):
96
+ parse_from_yaml(StringIO(data))
97
+
98
+ def test_ambiguous_mapping_batch_size_default(self, caplog):
99
+ caplog.set_level(logging.WARNING)
100
+ base_path = Path(__file__).parent / "mapping_vanilla_sf.yml"
101
+ with open(base_path, "r") as f:
102
+ data = f.read().replace("table: Opportunity", "batch_size: 150")
103
+ data = data.replace("api: bulk", "")
104
+ parse_from_yaml(StringIO(data))
105
+
106
+ assert "If you set a `batch_size` you should also set" in caplog.text
107
+
108
+ def test_bad_mapping_batch_size_default(self, caplog):
109
+ caplog.set_level(logging.WARNING)
110
+ base_path = Path(__file__).parent / "mapping_vanilla_sf.yml"
111
+ with open(base_path, "r") as f:
112
+ data = f.read().replace("table: Opportunity", "batch_size: 1000")
113
+ data = f.read().replace("api: bulk", "")
114
+ with pytest.raises(ValidationError):
115
+ parse_from_yaml(StringIO(data))
116
+
117
+ def test_default_table_to_sobject_name(self):
118
+ base_path = Path(__file__).parent / "mapping_v3.yml"
119
+ with open(base_path, "r") as f:
120
+ data = f.read()
121
+ ms = parse_from_yaml(StringIO(data))
122
+ assert ms["Insert Accounts"].table == "Account"
123
+
124
+ def test_fields_list_to_dict(self):
125
+ base_path = Path(__file__).parent / "mapping_v3.yml"
126
+ with open(base_path, "r") as f:
127
+ data = f.read()
128
+ ms = parse_from_yaml(StringIO(data))
129
+ assert ms["Insert Accounts"].fields == {"Name": "Name"}
130
+ assert ms["Insert Contacts"].fields == {
131
+ "FirstName": "FirstName",
132
+ "LastName": "LastName",
133
+ "Email": "Email",
134
+ }
135
+
136
+ def test_fields_default_not_present(self):
137
+ base_path = Path(__file__).parent / "mapping_v3.yml"
138
+ with open(base_path, "r") as f:
139
+ data = f.read()
140
+ ms = parse_from_yaml(StringIO(data))
141
+ assert ms["Insert Junction Objects"].fields == {}
142
+
143
+ def test_fields_default_null(self):
144
+ base_path = Path(__file__).parent / "mapping_v3.yml"
145
+ with open(base_path, "r") as f:
146
+ data = f.read()
147
+ ms = parse_from_yaml(StringIO(data))
148
+ assert ms["Insert Other Junction Objects"].fields == {}
149
+
150
+ def test_load_from_bytes_stream(self):
151
+ base_path = Path(__file__).parent / "mapping_v2.yml"
152
+ with open(base_path, "rb") as f:
153
+ assert parse_from_yaml(f)
154
+
155
+ def test_get_complete_field_map(self):
156
+ m = MappingStep(
157
+ sf_object="Account",
158
+ fields=["Name", "AccountSite"],
159
+ lookups={"ParentId": MappingLookup(table="Account")},
160
+ )
161
+
162
+ assert m.get_complete_field_map() == {
163
+ "Name": "Name",
164
+ "AccountSite": "AccountSite",
165
+ "ParentId": "ParentId",
166
+ }
167
+ assert m.get_complete_field_map(include_id=True) == {
168
+ "Id": "sf_id",
169
+ "Name": "Name",
170
+ "AccountSite": "AccountSite",
171
+ "ParentId": "ParentId",
172
+ }
173
+
174
+ def test_get_relative_date_context(self):
175
+ mapping = MappingStep(
176
+ sf_object="Account",
177
+ fields=["Some_Date__c", "Some_Datetime__c"],
178
+ anchor_date="2020-07-01",
179
+ )
180
+
181
+ salesforce_client = mock.Mock()
182
+ salesforce_client.Account.describe.return_value = {
183
+ "fields": [
184
+ {"name": "Some_Date__c", "type": "date"},
185
+ {"name": "Some_Datetime__c", "type": "datetime"},
186
+ {"name": "Some_Bool__c", "type": "boolean"},
187
+ ]
188
+ }
189
+
190
+ assert mapping.get_relative_date_context(
191
+ mapping.get_load_field_list(), salesforce_client
192
+ ) == ([0], [1], date.today())
193
+
194
+ def test_get_relative_date_e2e(self):
195
+ base_path = Path(__file__).parent / "mapping_v1.yml"
196
+ mapping = parse_from_yaml(base_path)
197
+ salesforce_client = mock.Mock()
198
+ salesforce_client.Contact.describe.return_value = {
199
+ "fields": [
200
+ {"name": "Some_Date__c", "type": "date"},
201
+ {"name": "Some_Datetime__c", "type": "datetime"},
202
+ {"name": "Some_Bool__c", "type": "boolean"},
203
+ ]
204
+ }
205
+ contacts_mapping = mapping["Insert Contacts"]
206
+ contacts_mapping.fields.update(
207
+ {"Some_Date__c": "Some_Date__c", "Some_Datetime__c": "Some_Datetime__c"}
208
+ )
209
+ assert contacts_mapping.get_relative_date_context(
210
+ contacts_mapping.get_load_field_list(), salesforce_client
211
+ ) == (
212
+ [3],
213
+ [4],
214
+ date.today(),
215
+ )
216
+
217
+ def test_select_options__success(self):
218
+ base_path = Path(__file__).parent / "mapping_select.yml"
219
+ result = parse_from_yaml(base_path)
220
+
221
+ step = result["Select Accounts"]
222
+ select_options = step.select_options
223
+ assert select_options
224
+ assert select_options.strategy == SelectStrategy.SIMILARITY
225
+ assert select_options.filter == "WHEN Name in ('Sample Account')"
226
+ assert select_options.priority_fields
227
+
228
+ def test_select_options__invalid_strategy(self):
229
+ base_path = Path(__file__).parent / "mapping_select_invalid_strategy.yml"
230
+ with pytest.raises(ValueError) as e:
231
+ parse_from_yaml(base_path)
232
+ assert "Invalid strategy value: invalid_strategy" in str(e.value)
233
+
234
+ def test_select_options__invalid_threshold__non_float(self):
235
+ base_path = (
236
+ Path(__file__).parent / "mapping_select_invalid_threshold__non_float.yml"
237
+ )
238
+ with pytest.raises(ValueError) as e:
239
+ parse_from_yaml(base_path)
240
+ assert "value is not a valid float" in str(e.value)
241
+
242
+ def test_select_options__invalid_threshold__invalid_strategy(self):
243
+ base_path = (
244
+ Path(__file__).parent
245
+ / "mapping_select_invalid_threshold__invalid_strategy.yml"
246
+ )
247
+ with pytest.raises(ValueError) as e:
248
+ parse_from_yaml(base_path)
249
+ assert (
250
+ "If a threshold is specified, the strategy must be set to 'similarity'."
251
+ in str(e.value)
252
+ )
253
+
254
+ def test_select_options__invalid_threshold__invalid_number(self):
255
+ base_path = (
256
+ Path(__file__).parent
257
+ / "mapping_select_invalid_threshold__invalid_number.yml"
258
+ )
259
+ with pytest.raises(ValueError) as e:
260
+ parse_from_yaml(base_path)
261
+ assert "Threshold must be between 0 and 1, got 1.5" in str(e.value)
262
+
263
+ def test_select_options__missing_priority_fields(self):
264
+ base_path = Path(__file__).parent / "mapping_select_missing_priority_fields.yml"
265
+ with pytest.raises(ValueError) as e:
266
+ parse_from_yaml(base_path)
267
+ print(str(e.value))
268
+ assert (
269
+ "Priority fields {'Email'} are not present in 'fields' or 'lookups'"
270
+ in str(e.value)
271
+ )
272
+
273
+ def test_select_options__no_priority_fields(self):
274
+ base_path = Path(__file__).parent / "mapping_select_no_priority_fields.yml"
275
+ result = parse_from_yaml(base_path)
276
+
277
+ step = result["Select Accounts"]
278
+ select_options = step.select_options
279
+ assert select_options.priority_fields == {}
280
+
281
+ # Start of FLS/Namespace Injection Unit Tests
282
+
283
+ def test_is_injectable(self):
284
+ assert MappingStep._is_injectable("Test__c")
285
+ assert not MappingStep._is_injectable("npsp__Test__c")
286
+ assert not MappingStep._is_injectable("Account")
287
+
288
+ def test_get_permission_types(self):
289
+ ms = MappingStep(
290
+ sf_object="Account", fields=["Name"], action=DataOperationType.INSERT
291
+ )
292
+ assert ms._get_required_permission_types(DataOperationType.INSERT) == (
293
+ "createable",
294
+ )
295
+ assert ms._get_required_permission_types(DataOperationType.QUERY) == (
296
+ "queryable",
297
+ )
298
+
299
+ ms = MappingStep(
300
+ sf_object="Account", fields=["Name"], action=DataOperationType.UPDATE
301
+ )
302
+ assert ms._get_required_permission_types(DataOperationType.INSERT) == (
303
+ "updateable",
304
+ )
305
+
306
+ ms = MappingStep(
307
+ sf_object="Account",
308
+ fields=["Name", "Extid__c"],
309
+ action=DataOperationType.UPSERT,
310
+ update_key="Extid__c",
311
+ )
312
+ assert ms._get_required_permission_types(DataOperationType.UPSERT) == (
313
+ "updateable",
314
+ "createable",
315
+ )
316
+
317
+ def test_check_field_permission(self):
318
+ ms = MappingStep(
319
+ sf_object="Account", fields=["Name"], action=DataOperationType.INSERT
320
+ )
321
+
322
+ assert ms._check_field_permission(
323
+ {"Name": {"createable": True}}, "Name", DataOperationType.INSERT
324
+ )
325
+
326
+ assert ms._check_field_permission(
327
+ {"Name": {"createable": True}}, "Name", DataOperationType.QUERY
328
+ )
329
+
330
+ ms = MappingStep(
331
+ sf_object="Account", fields=["Name"], action=DataOperationType.UPDATE
332
+ )
333
+
334
+ assert not ms._check_field_permission(
335
+ {"Name": {"updateable": False}}, "Name", DataOperationType.INSERT
336
+ )
337
+
338
+ assert not ms._check_field_permission(
339
+ {"Name": {"updateable": False}}, "Website", DataOperationType.INSERT
340
+ )
341
+
342
+ def test_validate_field_dict__fls_checks(self):
343
+ ms = MappingStep(
344
+ sf_object="Account",
345
+ fields=["Id", "Name", "Website"],
346
+ action=DataOperationType.INSERT,
347
+ )
348
+
349
+ assert ms._validate_field_dict(
350
+ describe=CaseInsensitiveDict(
351
+ {"Name": {"createable": True}, "Website": {"createable": True}}
352
+ ),
353
+ field_dict=ms.fields_,
354
+ inject=None,
355
+ strip=None,
356
+ drop_missing=False,
357
+ data_operation_type=DataOperationType.INSERT,
358
+ )
359
+
360
+ assert not ms._validate_field_dict(
361
+ describe=CaseInsensitiveDict(
362
+ {"Name": {"createable": True}, "Website": {"createable": False}}
363
+ ),
364
+ field_dict=ms.fields_,
365
+ inject=None,
366
+ strip=None,
367
+ drop_missing=False,
368
+ data_operation_type=DataOperationType.INSERT,
369
+ )
370
+
371
+ def test_validate_field_dict__injection(self):
372
+ ms = MappingStep(
373
+ sf_object="Account",
374
+ fields=["Id", "Name", "Test__c"],
375
+ action=DataOperationType.INSERT,
376
+ )
377
+
378
+ assert ms._validate_field_dict(
379
+ describe=CaseInsensitiveDict(
380
+ {"Name": {"createable": True}, "npsp__Test__c": {"createable": True}}
381
+ ),
382
+ field_dict=ms.fields_,
383
+ inject=lambda field: f"npsp__{field}",
384
+ strip=None,
385
+ drop_missing=False,
386
+ data_operation_type=DataOperationType.INSERT,
387
+ )
388
+
389
+ assert ms.fields_ == {"Id": "Id", "Name": "Name", "npsp__Test__c": "Test__c"}
390
+
391
+ def test_validate_fields_required(self):
392
+ ms = MappingStep(
393
+ sf_object="Account",
394
+ fields=["Id", "Name", "Test__c"],
395
+ action=DataOperationType.INSERT,
396
+ )
397
+ fields_describe = CaseInsensitiveDict(
398
+ {
399
+ "Name": {
400
+ "createable": True,
401
+ "nillable": False,
402
+ "defaultedOnCreate": False,
403
+ "defaultValue": None,
404
+ },
405
+ "npsp__Test__c": {
406
+ "createable": True,
407
+ "nillable": False,
408
+ "defaultedOnCreate": False,
409
+ "defaultValue": None,
410
+ },
411
+ }
412
+ )
413
+ ms._validate_field_dict(
414
+ describe=fields_describe,
415
+ field_dict=ms.fields_,
416
+ inject=lambda field: f"npsp__{field}",
417
+ strip=None,
418
+ drop_missing=False,
419
+ data_operation_type=DataOperationType.INSERT,
420
+ )
421
+ assert ms.fields_ == {"Id": "Id", "Name": "Name", "npsp__Test__c": "Test__c"}
422
+ assert ms.check_required(fields_describe)
423
+
424
+ def test_validate_fields_required_missing(self):
425
+ ms = MappingStep(
426
+ sf_object="Account",
427
+ fields=["Test__c"],
428
+ action=DataOperationType.INSERT,
429
+ )
430
+ fields_describe = CaseInsensitiveDict(
431
+ {
432
+ "Name": {
433
+ "createable": True,
434
+ "nillable": False,
435
+ "defaultedOnCreate": False,
436
+ "defaultValue": None,
437
+ },
438
+ "Test__c": {
439
+ "createable": True,
440
+ "nillable": False,
441
+ "defaultedOnCreate": False,
442
+ "defaultValue": None,
443
+ },
444
+ }
445
+ )
446
+ assert ms.fields_ == {"Test__c": "Test__c"}
447
+ assert not ms.check_required(fields_describe)
448
+
449
+ def test_validate_field_dict__injection_duplicate_fields(self):
450
+ ms = MappingStep(
451
+ sf_object="Account",
452
+ fields=["Id", "Name", "Test__c"],
453
+ action=DataOperationType.INSERT,
454
+ )
455
+
456
+ assert ms._validate_field_dict(
457
+ describe=CaseInsensitiveDict(
458
+ {
459
+ "Name": {"createable": True},
460
+ "npsp__Test__c": {"createable": True},
461
+ "Test__c": {"createable": True},
462
+ }
463
+ ),
464
+ field_dict=ms.fields_,
465
+ inject=lambda field: f"npsp__{field}",
466
+ strip=None,
467
+ drop_missing=False,
468
+ data_operation_type=DataOperationType.INSERT,
469
+ )
470
+
471
+ assert ms.fields_ == {"Id": "Id", "Name": "Name", "Test__c": "Test__c"}
472
+
473
+ def test_validate_field_dict__drop_missing(self):
474
+ ms = MappingStep(
475
+ sf_object="Account",
476
+ fields=["Id", "Name", "Website"],
477
+ action=DataOperationType.INSERT,
478
+ )
479
+
480
+ assert ms._validate_field_dict(
481
+ describe=CaseInsensitiveDict(
482
+ {"Name": {"createable": True}, "Website": {"createable": False}}
483
+ ),
484
+ field_dict=ms.fields_,
485
+ inject=None,
486
+ strip=None,
487
+ drop_missing=True,
488
+ data_operation_type=DataOperationType.INSERT,
489
+ )
490
+
491
+ assert ms.fields_ == {"Id": "Id", "Name": "Name"}
492
+
493
+ def test_validate_sobject(self):
494
+ ms = MappingStep(
495
+ sf_object="Account", fields=["Name"], action=DataOperationType.INSERT
496
+ )
497
+
498
+ assert ms._validate_sobject(
499
+ CaseInsensitiveDict({"Account": {"createable": True}}),
500
+ None,
501
+ None,
502
+ DataOperationType.INSERT,
503
+ )
504
+
505
+ assert ms._validate_sobject(
506
+ CaseInsensitiveDict({"Account": {"queryable": True}}),
507
+ None,
508
+ None,
509
+ DataOperationType.QUERY,
510
+ )
511
+
512
+ ms = MappingStep(
513
+ sf_object="Account", fields=["Name"], action=DataOperationType.UPDATE
514
+ )
515
+
516
+ assert not ms._validate_sobject(
517
+ CaseInsensitiveDict({"Account": {"updateable": False}}),
518
+ None,
519
+ None,
520
+ DataOperationType.INSERT,
521
+ )
522
+
523
+ def test_validate_sobject__injection(self):
524
+ ms = MappingStep(
525
+ sf_object="Test__c", fields=["Name"], action=DataOperationType.INSERT
526
+ )
527
+
528
+ assert ms._validate_sobject(
529
+ CaseInsensitiveDict({"npsp__Test__c": {"createable": True}}),
530
+ inject=lambda obj: f"npsp__{obj}",
531
+ strip=None,
532
+ data_operation_type=DataOperationType.INSERT,
533
+ )
534
+ assert ms.sf_object == "npsp__Test__c"
535
+
536
+ def test_validate_sobject__stripping(self):
537
+ ms = MappingStep(
538
+ sf_object="foo__Test__c", fields=["Name"], action=DataOperationType.INSERT
539
+ )
540
+
541
+ assert ms._validate_sobject(
542
+ CaseInsensitiveDict({"Test__c": {"createable": True}}),
543
+ inject=None,
544
+ strip=lambda obj: obj[len("foo__") :],
545
+ data_operation_type=DataOperationType.INSERT,
546
+ )
547
+ assert ms.sf_object == "Test__c"
548
+
549
+ def test_validate_sobject__injection_duplicate(self):
550
+ ms = MappingStep(
551
+ sf_object="Test__c", fields=["Name"], action=DataOperationType.INSERT
552
+ )
553
+
554
+ assert ms._validate_sobject(
555
+ CaseInsensitiveDict(
556
+ {"npsp__Test__c": {"createable": True}, "Test__c": {"createable": True}}
557
+ ),
558
+ lambda obj: f"npsp__{obj}",
559
+ None,
560
+ DataOperationType.INSERT,
561
+ )
562
+ assert ms.sf_object == "Test__c"
563
+
564
+ @mock.patch(
565
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
566
+ return_value=True,
567
+ )
568
+ @mock.patch(
569
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
570
+ return_value=True,
571
+ )
572
+ def test_validate_and_inject_namespace__injection_fields(
573
+ self, mock_field, mock_sobject
574
+ ):
575
+ ms = parse_from_yaml(
576
+ StringIO(
577
+ """Insert Accounts:
578
+ sf_object: Account
579
+ table: Account
580
+ fields:
581
+ - Test__c"""
582
+ )
583
+ )["Insert Accounts"]
584
+
585
+ salesforce_client = mock.Mock()
586
+ salesforce_client.describe.return_value = {
587
+ "sobjects": [{"name": "Account", "createable": True}]
588
+ }
589
+ salesforce_client.Account.describe.return_value = {
590
+ "fields": [{"name": "ns__Test__c", "createable": True}]
591
+ }
592
+
593
+ assert ms.validate_and_inject_namespace(
594
+ salesforce_client, "ns", DataOperationType.INSERT, inject_namespaces=True
595
+ )
596
+
597
+ ms._validate_sobject.assert_called_once_with(
598
+ CaseInsensitiveDict({"Account": {"name": "Account", "createable": True}}),
599
+ mock.ANY, # This is a function def
600
+ mock.ANY,
601
+ DataOperationType.INSERT,
602
+ )
603
+
604
+ ms._validate_field_dict.assert_has_calls(
605
+ [
606
+ mock.call(
607
+ CaseInsensitiveDict(
608
+ {"ns__Test__c": {"name": "ns__Test__c", "createable": True}}
609
+ ),
610
+ ms.fields,
611
+ mock.ANY, # local function def
612
+ mock.ANY, # local function def
613
+ False,
614
+ DataOperationType.INSERT,
615
+ ),
616
+ mock.call(
617
+ {"ns__Test__c": {"name": "ns__Test__c", "createable": True}},
618
+ ms.lookups,
619
+ mock.ANY, # local function def
620
+ mock.ANY, # local function def
621
+ False,
622
+ DataOperationType.INSERT,
623
+ ),
624
+ ]
625
+ )
626
+
627
+ @mock.patch(
628
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
629
+ return_value=True,
630
+ )
631
+ @mock.patch(
632
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
633
+ return_value=True,
634
+ )
635
+ def test_validate_and_inject_namespace__injection_lookups(
636
+ self, mock_field, mock_sobject
637
+ ):
638
+ ms = parse_from_yaml(
639
+ StringIO(
640
+ """Insert Accounts:
641
+ sf_object: Account
642
+ table: Account
643
+ fields:
644
+ - Name
645
+ lookups:
646
+ Lookup__c:
647
+ table: Stuff"""
648
+ )
649
+ )["Insert Accounts"]
650
+
651
+ salesforce_client = mock.Mock()
652
+ salesforce_client.describe.return_value = {
653
+ "sobjects": [{"name": "Account", "createable": True}]
654
+ }
655
+ salesforce_client.Account.describe.return_value = {
656
+ "fields": [
657
+ {"name": "Name", "createable": True},
658
+ {"name": "ns__Lookup__c", "updateable": False, "createable": True},
659
+ ]
660
+ }
661
+
662
+ assert ms.validate_and_inject_namespace(
663
+ salesforce_client, "ns", DataOperationType.INSERT, inject_namespaces=True
664
+ )
665
+
666
+ ms._validate_sobject.assert_called_once_with(
667
+ CaseInsensitiveDict({"Account": {"name": "Account", "createable": True}}),
668
+ mock.ANY, # local function def
669
+ mock.ANY,
670
+ DataOperationType.INSERT,
671
+ )
672
+
673
+ ms._validate_field_dict.assert_has_calls(
674
+ [
675
+ mock.call(
676
+ {
677
+ "Name": {"name": "Name", "createable": True},
678
+ "ns__Lookup__c": {
679
+ "name": "ns__Lookup__c",
680
+ "updateable": False,
681
+ "createable": True,
682
+ },
683
+ },
684
+ ms.fields,
685
+ mock.ANY, # local function def.
686
+ mock.ANY, # local function def.
687
+ False,
688
+ DataOperationType.INSERT,
689
+ ),
690
+ mock.call(
691
+ {
692
+ "Name": {"name": "Name", "createable": True},
693
+ "ns__Lookup__c": {
694
+ "name": "ns__Lookup__c",
695
+ "updateable": False,
696
+ "createable": True,
697
+ },
698
+ },
699
+ ms.lookups,
700
+ mock.ANY, # local function def.
701
+ mock.ANY, # local function def.
702
+ False,
703
+ DataOperationType.INSERT,
704
+ ),
705
+ ]
706
+ )
707
+
708
+ @mock.patch(
709
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
710
+ return_value=True,
711
+ )
712
+ @mock.patch(
713
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
714
+ return_value=True,
715
+ )
716
+ def test_validate_and_inject_namespace__fls(self, mock_field, mock_sobject):
717
+ ms = MappingStep(
718
+ sf_object="Test__c", fields=["Field__c"], action=DataOperationType.INSERT
719
+ )
720
+
721
+ salesforce_client = mock.Mock()
722
+ salesforce_client.describe.return_value = {
723
+ "sobjects": [{"name": "Test__c", "createable": True}]
724
+ }
725
+ salesforce_client.Test__c.describe.return_value = {
726
+ "fields": [{"name": "Field__c", "createable": True}]
727
+ }
728
+ assert ms.validate_and_inject_namespace(
729
+ salesforce_client, "ns", DataOperationType.INSERT
730
+ )
731
+
732
+ ms._validate_sobject.assert_called_once_with(
733
+ CaseInsensitiveDict({"Test__c": {"name": "Test__c", "createable": True}}),
734
+ None,
735
+ None,
736
+ DataOperationType.INSERT,
737
+ )
738
+
739
+ ms._validate_field_dict.assert_has_calls(
740
+ [
741
+ mock.call(
742
+ {"Field__c": {"name": "Field__c", "createable": True}},
743
+ {"Field__c": "Field__c"},
744
+ None,
745
+ None,
746
+ False,
747
+ DataOperationType.INSERT,
748
+ ),
749
+ mock.call(
750
+ {"Field__c": {"name": "Field__c", "createable": True}},
751
+ {},
752
+ None,
753
+ None,
754
+ False,
755
+ DataOperationType.INSERT,
756
+ ),
757
+ ]
758
+ )
759
+
760
+ @mock.patch(
761
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
762
+ return_value=False,
763
+ )
764
+ @mock.patch(
765
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
766
+ return_value=True,
767
+ )
768
+ def test_validate_and_inject_namespace__fls_sobject_failure(
769
+ self, mock_field, mock_sobject
770
+ ):
771
+ ms = MappingStep(
772
+ sf_object="Test__c", fields=["Name"], action=DataOperationType.INSERT
773
+ )
774
+
775
+ salesforce_client = mock.Mock()
776
+ salesforce_client.describe.return_value = {
777
+ "sobjects": [{"name": "Test__c", "createable": False}]
778
+ }
779
+ salesforce_client.Test__c.describe.return_value = {
780
+ "fields": [{"name": "Name", "createable": True}]
781
+ }
782
+ assert not ms.validate_and_inject_namespace(
783
+ salesforce_client, "ns", DataOperationType.INSERT
784
+ )
785
+
786
+ ms._validate_sobject.assert_called_once_with(
787
+ {"Test__c": {"name": "Test__c", "createable": False}},
788
+ None,
789
+ None,
790
+ DataOperationType.INSERT,
791
+ )
792
+
793
+ ms._validate_field_dict.assert_not_called()
794
+
795
+ @mock.patch(
796
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
797
+ return_value=True,
798
+ )
799
+ @mock.patch(
800
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
801
+ return_value=False,
802
+ )
803
+ def test_validate_and_inject_namespace__fls_fields_failure(
804
+ self, mock_field, mock_sobject
805
+ ):
806
+ ms = MappingStep(
807
+ sf_object="Test__c", fields=["Name"], action=DataOperationType.INSERT
808
+ )
809
+
810
+ salesforce_client = mock.Mock()
811
+ salesforce_client.describe.return_value = {
812
+ "sobjects": [{"name": "Test__c", "createable": True}]
813
+ }
814
+ salesforce_client.Test__c.describe.return_value = {
815
+ "fields": [{"name": "Name", "createable": False}]
816
+ }
817
+ assert not ms.validate_and_inject_namespace(
818
+ salesforce_client, "ns", DataOperationType.INSERT
819
+ )
820
+
821
+ ms._validate_sobject.assert_called_once_with(
822
+ {"Test__c": {"name": "Test__c", "createable": True}},
823
+ None,
824
+ None,
825
+ DataOperationType.INSERT,
826
+ )
827
+
828
+ ms._validate_field_dict.assert_has_calls(
829
+ [
830
+ mock.call(
831
+ {"Name": {"name": "Name", "createable": False}},
832
+ {"Name": "Name"},
833
+ None,
834
+ None,
835
+ False,
836
+ DataOperationType.INSERT,
837
+ )
838
+ ]
839
+ )
840
+
841
+ @mock.patch(
842
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
843
+ return_value=True,
844
+ )
845
+ @mock.patch(
846
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
847
+ side_effect=[True, False],
848
+ )
849
+ def test_validate_and_inject_namespace__fls_lookups_failure(
850
+ self, mock_field, mock_sobject
851
+ ):
852
+ ms = parse_from_yaml(
853
+ StringIO(
854
+ """Insert Accounts:
855
+ sf_object: Account
856
+ table: Account
857
+ fields:
858
+ - Name
859
+ lookups:
860
+ Lookup__c:
861
+ table: Stuff"""
862
+ )
863
+ )["Insert Accounts"]
864
+
865
+ salesforce_client = mock.Mock()
866
+ salesforce_client.describe.return_value = {
867
+ "sobjects": [{"name": "Account", "createable": True}]
868
+ }
869
+ salesforce_client.Account.describe.return_value = {
870
+ "fields": [
871
+ {"name": "Name", "createable": True},
872
+ {"name": "Lookup__c", "updateable": True, "createable": False},
873
+ ]
874
+ }
875
+ assert not ms.validate_and_inject_namespace(
876
+ salesforce_client, "ns", DataOperationType.INSERT
877
+ )
878
+
879
+ ms._validate_sobject.assert_called_once_with(
880
+ {"Account": {"name": "Account", "createable": True}},
881
+ None,
882
+ None,
883
+ DataOperationType.INSERT,
884
+ )
885
+
886
+ ms._validate_field_dict.assert_has_calls(
887
+ [
888
+ mock.call(
889
+ {
890
+ "Name": {"name": "Name", "createable": True},
891
+ "Lookup__c": {
892
+ "name": "Lookup__c",
893
+ "updateable": True,
894
+ "createable": False,
895
+ },
896
+ },
897
+ {"Name": "Name"},
898
+ None,
899
+ None,
900
+ False,
901
+ DataOperationType.INSERT,
902
+ ),
903
+ mock.call(
904
+ {
905
+ "Name": {"name": "Name", "createable": True},
906
+ "Lookup__c": {
907
+ "name": "Lookup__c",
908
+ "updateable": True,
909
+ "createable": False,
910
+ },
911
+ },
912
+ ms.lookups,
913
+ None,
914
+ None,
915
+ False,
916
+ DataOperationType.INSERT,
917
+ ),
918
+ ]
919
+ )
920
+
921
+ @mock.patch(
922
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_sobject",
923
+ return_value=True,
924
+ )
925
+ @mock.patch(
926
+ "cumulusci.tasks.bulkdata.mapping_parser.MappingStep._validate_field_dict",
927
+ side_effect=[True, False],
928
+ )
929
+ def test_validate_and_inject_namespace__fls_lookups_update_failure(
930
+ self, mock_field, mock_sobject
931
+ ):
932
+ ms = parse_from_yaml(
933
+ StringIO(
934
+ """Insert Accounts:
935
+ sf_object: Account
936
+ table: Account
937
+ fields:
938
+ - Name
939
+ lookups:
940
+ Lookup__c:
941
+ table: Stuff
942
+ after: Insert Stuff"""
943
+ )
944
+ )["Insert Accounts"]
945
+
946
+ salesforce_client = mock.Mock()
947
+ salesforce_client.describe.return_value = {
948
+ "sobjects": [{"name": "Account", "createable": True}]
949
+ }
950
+ salesforce_client.Account.describe.return_value = {
951
+ "fields": [
952
+ {"name": "Name", "createable": True},
953
+ {"name": "Lookup__c", "updateable": False, "createable": True},
954
+ ]
955
+ }
956
+ assert not ms.validate_and_inject_namespace(
957
+ salesforce_client, "ns", DataOperationType.INSERT
958
+ )
959
+
960
+ ms._validate_sobject.assert_called_once_with(
961
+ {"Account": {"name": "Account", "createable": True}},
962
+ None,
963
+ None,
964
+ DataOperationType.INSERT,
965
+ )
966
+
967
+ ms._validate_field_dict.assert_has_calls(
968
+ [
969
+ mock.call(
970
+ {
971
+ "Name": {"name": "Name", "createable": True},
972
+ "Lookup__c": {
973
+ "name": "Lookup__c",
974
+ "updateable": False,
975
+ "createable": True,
976
+ },
977
+ },
978
+ {"Name": "Name"},
979
+ None,
980
+ None,
981
+ False,
982
+ DataOperationType.INSERT,
983
+ ),
984
+ mock.call(
985
+ {
986
+ "Name": {"name": "Name", "createable": True},
987
+ "Lookup__c": {
988
+ "name": "Lookup__c",
989
+ "updateable": False,
990
+ "createable": True,
991
+ },
992
+ },
993
+ ms.lookups,
994
+ None,
995
+ None,
996
+ False,
997
+ DataOperationType.INSERT,
998
+ ),
999
+ ]
1000
+ )
1001
+
1002
+ # Start of FLS/Namespace Injection Integration Tests
1003
+
1004
+ @responses.activate
1005
+ def test_validate_and_inject_mapping_enforces_fls(self):
1006
+ mock_describe_calls()
1007
+ mapping = parse_from_yaml(
1008
+ StringIO(
1009
+ "Insert Accounts:\n sf_object: Account\n table: Account\n fields:\n - Nonsense__c"
1010
+ )
1011
+ )
1012
+ org_config = DummyOrgConfig(
1013
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1014
+ )
1015
+
1016
+ with pytest.raises(BulkDataException):
1017
+ validate_and_inject_mapping(
1018
+ mapping=mapping,
1019
+ sf=org_config.salesforce_client,
1020
+ namespace=None,
1021
+ data_operation=DataOperationType.INSERT,
1022
+ inject_namespaces=False,
1023
+ drop_missing=False,
1024
+ )
1025
+
1026
+ @responses.activate
1027
+ def test_validate_and_inject_mapping_removes_steps_with_drop_missing(self):
1028
+ mock_describe_calls()
1029
+ mapping = parse_from_yaml(
1030
+ StringIO(
1031
+ "Insert Accounts:\n sf_object: NotAccount\n table: Account\n fields:\n - Nonsense__c"
1032
+ )
1033
+ )
1034
+ org_config = DummyOrgConfig(
1035
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1036
+ )
1037
+
1038
+ validate_and_inject_mapping(
1039
+ mapping=mapping,
1040
+ sf=org_config.salesforce_client,
1041
+ namespace=None,
1042
+ data_operation=DataOperationType.INSERT,
1043
+ inject_namespaces=False,
1044
+ drop_missing=True,
1045
+ )
1046
+
1047
+ assert "Insert Accounts" not in mapping
1048
+
1049
+ @responses.activate
1050
+ def test_validate_and_inject_mapping_removes_lookups_with_drop_missing(self):
1051
+ mock_describe_calls()
1052
+ mapping = parse_from_yaml(
1053
+ StringIO(
1054
+ (
1055
+ "Insert Accounts:\n sf_object: NotAccount\n table: Account\n fields:\n - Nonsense__c\n"
1056
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n fields:\n - LastName\n lookups:\n AccountId:\n table: Account"
1057
+ )
1058
+ )
1059
+ )
1060
+ org_config = DummyOrgConfig(
1061
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1062
+ )
1063
+
1064
+ validate_and_inject_mapping(
1065
+ mapping=mapping,
1066
+ sf=org_config.salesforce_client,
1067
+ namespace=None,
1068
+ data_operation=DataOperationType.INSERT,
1069
+ inject_namespaces=False,
1070
+ drop_missing=True,
1071
+ )
1072
+
1073
+ assert "Insert Accounts" not in mapping
1074
+ assert "Insert Contacts" in mapping
1075
+ assert "AccountId" not in mapping["Insert Contacts"].lookups
1076
+
1077
+ @responses.activate
1078
+ def test_validate_and_inject_mapping_removes_lookups_with_drop_missing__polymorphic_partial_present(
1079
+ self,
1080
+ ):
1081
+ mock_describe_calls()
1082
+ mapping = parse_from_yaml(
1083
+ StringIO(
1084
+ (
1085
+ "Insert Accounts:\n sf_object: NotAccount\n table: Account\n fields:\n - Nonsense__c\n"
1086
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n lookups:\n AccountId:\n table: Account\n"
1087
+ "Insert Events:\n sf_object: Event\n table: Event\n lookups:\n WhoId:\n table:\n - Contact\n - Lead"
1088
+ )
1089
+ )
1090
+ )
1091
+ org_config = DummyOrgConfig(
1092
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1093
+ )
1094
+
1095
+ validate_and_inject_mapping(
1096
+ mapping=mapping,
1097
+ sf=org_config.salesforce_client,
1098
+ namespace=None,
1099
+ data_operation=DataOperationType.QUERY,
1100
+ inject_namespaces=False,
1101
+ drop_missing=True,
1102
+ )
1103
+
1104
+ assert "Insert Accounts" not in mapping
1105
+ assert "Insert Contacts" in mapping
1106
+ assert "Insert Events" in mapping
1107
+ assert "AccountId" not in mapping["Insert Contacts"].lookups
1108
+ assert "WhoId" in mapping["Insert Events"].lookups
1109
+
1110
+ @responses.activate
1111
+ def test_validate_and_inject_mapping_removes_lookups_with_drop_missing__polymorphic_none_present(
1112
+ self,
1113
+ ):
1114
+ mock_describe_calls()
1115
+ mapping = parse_from_yaml(
1116
+ StringIO(
1117
+ (
1118
+ "Insert Contacts:\n sf_object: NotContact\n table: NotContact\n fields:\n - LastName\n"
1119
+ "Insert Leads:\n sf_object: NotLead\n table: NotLead\n fields:\n - LastName\n - Company\n"
1120
+ "Insert Events:\n sf_object: Event\n table: Event\n lookups:\n WhoId:\n table:\n - Contact\n - Lead"
1121
+ )
1122
+ )
1123
+ )
1124
+ org_config = DummyOrgConfig(
1125
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1126
+ )
1127
+
1128
+ validate_and_inject_mapping(
1129
+ mapping=mapping,
1130
+ sf=org_config.salesforce_client,
1131
+ namespace=None,
1132
+ data_operation=DataOperationType.QUERY,
1133
+ inject_namespaces=False,
1134
+ drop_missing=True,
1135
+ )
1136
+
1137
+ assert "Insert Contacts" not in mapping
1138
+ assert "Insert Leads" not in mapping
1139
+ assert "Insert Events" in mapping
1140
+ assert "WhoId" not in mapping["Insert Events"].lookups
1141
+
1142
+ @responses.activate
1143
+ def test_validate_and_inject_mapping_throws_exception_required_lookup_dropped(self):
1144
+ mock_describe_calls()
1145
+
1146
+ # This test uses a bit of gimmickry to validate exception behavior on dropping a required lookup.
1147
+ # Since mapping_parser identifies target objects via the `table` clause rather than the actual schema,
1148
+ # which makes us resilient to polymorphic lookups, we'll pretend the non-nillable `Id` field is a lookup.
1149
+ mapping = parse_from_yaml(
1150
+ StringIO(
1151
+ (
1152
+ "Insert Accounts:\n sf_object: NotAccount\n table: Account\n fields:\n - Nonsense__c\n"
1153
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n fields:\n - LastName\n lookups:\n Id:\n table: Account"
1154
+ )
1155
+ )
1156
+ )
1157
+ org_config = DummyOrgConfig(
1158
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1159
+ )
1160
+
1161
+ with pytest.raises(BulkDataException):
1162
+ validate_and_inject_mapping(
1163
+ mapping=mapping,
1164
+ sf=org_config.salesforce_client,
1165
+ namespace=None,
1166
+ data_operation=DataOperationType.INSERT,
1167
+ inject_namespaces=False,
1168
+ drop_missing=True,
1169
+ )
1170
+
1171
+ @responses.activate
1172
+ def test_validate_and_inject_mapping_throws_exception_required_fields_missing(
1173
+ self, caplog
1174
+ ):
1175
+ caplog.set_level(logging.ERROR)
1176
+ mock_describe_calls()
1177
+ mapping = parse_from_yaml(
1178
+ StringIO(
1179
+ (
1180
+ "Insert Accounts:\n sf_object: Account\n table: Account\n fields:\n - ns__Description__c\n"
1181
+ )
1182
+ )
1183
+ )
1184
+ org_config = DummyOrgConfig(
1185
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1186
+ )
1187
+
1188
+ validate_and_inject_mapping(
1189
+ mapping=mapping,
1190
+ sf=org_config.salesforce_client,
1191
+ namespace="",
1192
+ data_operation=DataOperationType.INSERT,
1193
+ inject_namespaces=False,
1194
+ drop_missing=False,
1195
+ )
1196
+
1197
+ expected_error_message = (
1198
+ "One or more required fields are missing for loading on Account :{'Name'}"
1199
+ )
1200
+ error_logs = [
1201
+ record.message for record in caplog.records if record.levelname == "ERROR"
1202
+ ]
1203
+ assert any(expected_error_message in error_log for error_log in error_logs)
1204
+
1205
+ @responses.activate
1206
+ def test_validate_and_inject_mapping_injects_namespaces(self):
1207
+ mock_describe_calls()
1208
+ # Note: ns__Description__c is a mock field added to our stored, mock describes (in JSON)
1209
+ ms = parse_from_yaml(
1210
+ StringIO(
1211
+ """Insert Accounts:
1212
+ sf_object: Account
1213
+ table: Account
1214
+ fields:
1215
+ - Description__c"""
1216
+ )
1217
+ )["Insert Accounts"]
1218
+ org_config = DummyOrgConfig(
1219
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1220
+ )
1221
+
1222
+ assert ms.validate_and_inject_namespace(
1223
+ org_config.salesforce_client,
1224
+ "ns",
1225
+ DataOperationType.INSERT,
1226
+ inject_namespaces=True,
1227
+ )
1228
+
1229
+ assert list(ms.fields.keys()) == ["ns__Description__c"]
1230
+
1231
+ @responses.activate
1232
+ def test_validate_and_inject_mapping_injects_namespaces__validates_lookup(self):
1233
+ """Test to verify that with namespace inject, we validate lookups correctly"""
1234
+ mock_describe_calls()
1235
+ # Note: ns__Description__c is a mock field added to our stored, mock describes (in JSON)
1236
+ mapping = parse_from_yaml(
1237
+ StringIO(
1238
+ """Insert Accounts:
1239
+ sf_object: Account
1240
+ table: Account
1241
+ fields:
1242
+ - Description__c
1243
+ lookups:
1244
+ LinkedAccount__c:
1245
+ table: Account"""
1246
+ )
1247
+ )
1248
+ ms = mapping["Insert Accounts"]
1249
+ org_config = DummyOrgConfig(
1250
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1251
+ )
1252
+
1253
+ assert ms.validate_and_inject_namespace(
1254
+ org_config.salesforce_client,
1255
+ "ns",
1256
+ DataOperationType.INSERT,
1257
+ inject_namespaces=True,
1258
+ )
1259
+ # Here we verify that the field ns__LinkedAccount__c does lookup
1260
+ # to sobject Account inside of describe
1261
+ _infer_and_validate_lookups(mapping, org_config.salesforce_client)
1262
+ assert list(ms.lookups.keys()) == ["ns__LinkedAccount__c"]
1263
+
1264
+ @responses.activate
1265
+ def test_validate_and_inject_mapping_removes_namespaces(self):
1266
+ mock_describe_calls()
1267
+ # Note: History__c is a mock field added to our stored, mock describes (in JSON)
1268
+ ms = parse_from_yaml(
1269
+ StringIO(
1270
+ """Insert Accounts:
1271
+ sf_object: Account
1272
+ table: Account
1273
+ fields:
1274
+ - ns__History__c"""
1275
+ )
1276
+ )["Insert Accounts"]
1277
+ org_config = DummyOrgConfig(
1278
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1279
+ )
1280
+
1281
+ assert ms.validate_and_inject_namespace(
1282
+ org_config.salesforce_client,
1283
+ "ns",
1284
+ DataOperationType.INSERT,
1285
+ inject_namespaces=True,
1286
+ )
1287
+
1288
+ assert list(ms.fields.keys()) == ["History__c"]
1289
+
1290
+ @responses.activate
1291
+ def test_validate_and_inject_mapping_queries_is_person_account_field(self):
1292
+ mock_describe_calls()
1293
+ mapping = parse_from_yaml(
1294
+ StringIO(
1295
+ (
1296
+ "Insert Accounts:\n sf_object: Account\n table: Account\n fields:\n - Description__c\n"
1297
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n lookups:\n AccountId:\n table: Account"
1298
+ )
1299
+ )
1300
+ )
1301
+ org_config = DummyOrgConfig(
1302
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1303
+ )
1304
+
1305
+ validate_and_inject_mapping(
1306
+ mapping=mapping,
1307
+ sf=org_config.salesforce_client,
1308
+ namespace=None,
1309
+ data_operation=DataOperationType.QUERY,
1310
+ inject_namespaces=False,
1311
+ drop_missing=True,
1312
+ org_has_person_accounts_enabled=True,
1313
+ )
1314
+
1315
+ assert "Insert Accounts" in mapping
1316
+ assert "Insert Contacts" in mapping
1317
+ assert "IsPersonAccount" in mapping["Insert Accounts"]["fields"]
1318
+ assert "IsPersonAccount" in mapping["Insert Contacts"]["fields"]
1319
+
1320
+
1321
+ class TestMappingLookup:
1322
+ def test_get_lookup_key_field__no_model(self):
1323
+ lookup = MappingLookup(table="contact", name="AccountId")
1324
+ assert lookup.get_lookup_key_field() == "AccountId"
1325
+
1326
+ def test_get_lookup_key_field__snake_case_model(self):
1327
+ class FakeModel:
1328
+ account_id = mock.MagicMock()
1329
+
1330
+ lookup = MappingLookup(table="contact", name="AccountId")
1331
+ assert lookup.get_lookup_key_field(FakeModel()) == "account_id"
1332
+
1333
+ def test_get_lookup_key_field__by_key_field(self):
1334
+ class FakeModel:
1335
+ foo = mock.MagicMock()
1336
+
1337
+ lookup = MappingLookup(table="contact", key_field="foo", name="AccountId")
1338
+ assert lookup.get_lookup_key_field(FakeModel()) == "foo"
1339
+
1340
+ def test_get_lookup_key_field__by_key_field_wrong_case(self):
1341
+ class FakeModel:
1342
+ account_id = mock.MagicMock()
1343
+
1344
+ # we can correct mismatched mapping files if the mistake is just
1345
+ # old-fashioned SQL with new Mapping File
1346
+ lookup = MappingLookup(table="contact", key_field="AccountId", name="AccountId")
1347
+ assert lookup.get_lookup_key_field(FakeModel()) == "account_id"
1348
+
1349
+ def test_get_lookup_key_field__mismatched_name(self):
1350
+ class FakeModel:
1351
+ account_id = mock.MagicMock()
1352
+
1353
+ # some mistakes can't be fixed.
1354
+ lookup = MappingLookup(table="contact", key_field="Foo", name="Foo")
1355
+
1356
+ with pytest.raises(KeyError):
1357
+ lookup.get_lookup_key_field(FakeModel())
1358
+
1359
+ @responses.activate
1360
+ def test_validate_and_inject_mapping_works_case_insensitively(self):
1361
+ mock_describe_calls()
1362
+ mapping = parse_from_yaml(
1363
+ StringIO(
1364
+ (
1365
+ "Insert Accounts:\n sf_object: account\n table: account\n fields:\n - name\n"
1366
+ "Insert Contacts:\n sf_object: contact\n table: contact\n fields:\n - LaSTnAME\n lookups:\n accountid:\n table: account"
1367
+ )
1368
+ )
1369
+ )
1370
+ org_config = DummyOrgConfig(
1371
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1372
+ )
1373
+
1374
+ assert mapping["Insert Accounts"].sf_object != "Account"
1375
+ assert mapping["Insert Accounts"].sf_object == "account"
1376
+ assert "name" in mapping["Insert Accounts"].fields
1377
+ assert "Name" not in mapping["Insert Accounts"].fields
1378
+
1379
+ validate_and_inject_mapping(
1380
+ mapping=mapping,
1381
+ sf=org_config.salesforce_client,
1382
+ namespace=None,
1383
+ data_operation=DataOperationType.INSERT,
1384
+ inject_namespaces=False,
1385
+ drop_missing=False,
1386
+ )
1387
+ assert mapping["Insert Accounts"].sf_object == "Account"
1388
+ assert mapping["Insert Accounts"].sf_object != "account"
1389
+ assert "Name" in mapping["Insert Accounts"].fields
1390
+ assert "name" not in mapping["Insert Accounts"].fields
1391
+
1392
+ @responses.activate
1393
+ def test_bulk_attributes(self):
1394
+ mapping = parse_from_yaml(
1395
+ StringIO(
1396
+ (
1397
+ """Insert Accounts:
1398
+ sf_object: account
1399
+ table: account
1400
+ api: rest
1401
+ bulk_mode: Serial
1402
+ batch_size: 50
1403
+ fields:
1404
+ - name"""
1405
+ )
1406
+ )
1407
+ )
1408
+ assert mapping["Insert Accounts"].api == DataApi.REST
1409
+ assert mapping["Insert Accounts"].bulk_mode == "Serial"
1410
+ assert mapping["Insert Accounts"].batch_size == 50
1411
+
1412
+ def test_case_conversions(self):
1413
+ mapping = parse_from_yaml(
1414
+ StringIO(
1415
+ (
1416
+ """Insert Accounts:
1417
+ sf_object: account
1418
+ table: account
1419
+ api: ReST
1420
+ bulk_mode: serial
1421
+ action: INSerT
1422
+ batch_size: 50
1423
+ fields:
1424
+ - name"""
1425
+ )
1426
+ )
1427
+ )
1428
+ assert mapping["Insert Accounts"].api == DataApi.REST
1429
+ assert mapping["Insert Accounts"].bulk_mode == "Serial"
1430
+ assert mapping["Insert Accounts"].action.value == "insert"
1431
+ assert mapping["Insert Accounts"].batch_size == 50
1432
+
1433
+ def test_oid_as_pk__raises(self):
1434
+ with pytest.raises(ValueError):
1435
+ parse_from_yaml(
1436
+ StringIO(
1437
+ (
1438
+ """Insert Accounts:
1439
+ sf_object: account
1440
+ oid_as_pk: True
1441
+ fields:
1442
+ - name"""
1443
+ )
1444
+ )
1445
+ )
1446
+
1447
+ def test_oid_as_pk__false(self):
1448
+ result = parse_from_yaml(
1449
+ StringIO(
1450
+ (
1451
+ """Insert Accounts:
1452
+ sf_object: account
1453
+ oid_as_pk: False
1454
+ fields:
1455
+ - name"""
1456
+ )
1457
+ )
1458
+ )
1459
+ assert result["Insert Accounts"].oid_as_pk is False
1460
+
1461
+
1462
+ class TestUpsertKeyValidations:
1463
+ def test_upsert_key_wrong_type(self):
1464
+ with pytest.raises(ValidationError) as e:
1465
+ parse_from_yaml(
1466
+ StringIO(
1467
+ (
1468
+ """Insert Accounts:
1469
+ sf_object: account
1470
+ table: account
1471
+ action: upsert
1472
+ update_key: 11
1473
+ fields:
1474
+ - name"""
1475
+ )
1476
+ )
1477
+ )
1478
+ assert "update_key" in str(e.value)
1479
+
1480
+ def test_upsert_key_wrong_type__list_item(self):
1481
+ with pytest.raises(ValidationError) as e:
1482
+ parse_from_yaml(
1483
+ StringIO(
1484
+ (
1485
+ """Insert Accounts:
1486
+ sf_object: account
1487
+ table: account
1488
+ action: upsert
1489
+ update_key:
1490
+ - 11
1491
+ fields:
1492
+ - name"""
1493
+ )
1494
+ )
1495
+ )
1496
+ assert "update_key" in str(e.value)
1497
+
1498
+ def test_upsert_key_list(self):
1499
+ mapping = parse_from_yaml(
1500
+ StringIO(
1501
+ (
1502
+ """Insert Accounts:
1503
+ sf_object: account
1504
+ table: account
1505
+ action: etl_upsert
1506
+ update_key:
1507
+ - FirstName
1508
+ - LastName
1509
+ fields:
1510
+ - FirstName
1511
+ - LastName """
1512
+ )
1513
+ )
1514
+ )
1515
+ assert mapping["Insert Accounts"]["update_key"] == (
1516
+ "FirstName",
1517
+ "LastName",
1518
+ ), mapping["Insert Accounts"]["update_key"]
1519
+
1520
+ def test_get_extract_field_list(self):
1521
+ """Test to ensure Id comes first, lookups come
1522
+ last and order of mappings do not change"""
1523
+ m = MappingStep(
1524
+ sf_object="Account",
1525
+ fields=["Name", "RecordTypeId", "AccountSite"],
1526
+ lookups={"ParentId": MappingLookup(table="Account")},
1527
+ )
1528
+
1529
+ assert m.get_extract_field_list() == [
1530
+ "Id",
1531
+ "Name",
1532
+ "RecordTypeId",
1533
+ "AccountSite",
1534
+ "ParentId",
1535
+ ]
1536
+
1537
+ @responses.activate
1538
+ def test_infer_and_validate_lookups__table_doesnt_exist(self, caplog):
1539
+ caplog.set_level(logging.ERROR)
1540
+ mock_describe_calls()
1541
+ mapping = parse_from_yaml(
1542
+ StringIO(
1543
+ (
1544
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n lookups:\n AccountId:\n table: Account"
1545
+ )
1546
+ )
1547
+ )
1548
+ org_config = DummyOrgConfig(
1549
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1550
+ )
1551
+
1552
+ expected_error_message = "The table Account does not exist in the mapping file"
1553
+
1554
+ with pytest.raises(BulkDataException) as e:
1555
+ _infer_and_validate_lookups(
1556
+ mapping=mapping, sf=org_config.salesforce_client
1557
+ )
1558
+ assert "One or more relationship errors blocked the operation" in str(
1559
+ e.value
1560
+ )
1561
+ error_logs = [
1562
+ record.message for record in caplog.records if record.levelname == "ERROR"
1563
+ ]
1564
+ assert any(expected_error_message in error_log for error_log in error_logs)
1565
+
1566
+ @responses.activate
1567
+ def test_infer_and_validate_lookups__incorrect_order(self, caplog):
1568
+ caplog.set_level(logging.ERROR)
1569
+ mock_describe_calls()
1570
+ mapping = parse_from_yaml(
1571
+ StringIO(
1572
+ (
1573
+ "Insert Account:\n"
1574
+ " sf_object: Account\n"
1575
+ " table: Account\n"
1576
+ " fields:\n"
1577
+ " - Description__c\n"
1578
+ "Insert Events:\n"
1579
+ " sf_object: Event\n"
1580
+ " table: Event\n"
1581
+ " lookups:\n"
1582
+ " WhatId:\n"
1583
+ " table:\n"
1584
+ " - Opportunity\n"
1585
+ " - Account\n"
1586
+ "Insert Opportunity:\n"
1587
+ " sf_object: Opportunity\n"
1588
+ " table: Opportunity\n"
1589
+ " fields:\n"
1590
+ " - Description__c\n"
1591
+ )
1592
+ )
1593
+ )
1594
+ org_config = DummyOrgConfig(
1595
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1596
+ )
1597
+
1598
+ expected_error_message = "All included target objects (Opportunity,Account) for the field Event.WhatId must precede Event in the mapping."
1599
+
1600
+ with pytest.raises(BulkDataException) as e:
1601
+ _infer_and_validate_lookups(
1602
+ mapping=mapping, sf=org_config.salesforce_client
1603
+ )
1604
+ assert "One or more relationship errors blocked the operation" in str(
1605
+ e.value
1606
+ )
1607
+ error_logs = [
1608
+ record.message for record in caplog.records if record.levelname == "ERROR"
1609
+ ]
1610
+ assert any(expected_error_message in error_log for error_log in error_logs)
1611
+
1612
+ @responses.activate
1613
+ def test_infer_and_validate_lookups__after(self):
1614
+ mock_describe_calls()
1615
+ mapping = parse_from_yaml(
1616
+ StringIO(
1617
+ (
1618
+ "Insert Accounts:\n sf_object: Account\n table: Account\n lookups:\n ParentId:\n table: Account\n"
1619
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n lookups:\n AccountId:\n table: Account"
1620
+ )
1621
+ )
1622
+ )
1623
+ org_config = DummyOrgConfig(
1624
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1625
+ )
1626
+ _infer_and_validate_lookups(mapping=mapping, sf=org_config.salesforce_client)
1627
+ assert mapping["Insert Accounts"].lookups["ParentId"].after == "Insert Accounts"
1628
+ assert mapping["Insert Contacts"].lookups["AccountId"].after is None
1629
+
1630
+ @responses.activate
1631
+ def test_infer_and_validate_lookups__invalid_reference(self, caplog):
1632
+ caplog.set_level(logging.ERROR)
1633
+ mock_describe_calls()
1634
+ mapping = parse_from_yaml(
1635
+ StringIO(
1636
+ (
1637
+ "Insert Events:\n sf_object: Event\n table: Event\n fields:\n - Description__c\n"
1638
+ "Insert Contacts:\n sf_object: Contact\n table: Contact\n lookups:\n AccountId:\n table: Event"
1639
+ )
1640
+ )
1641
+ )
1642
+ org_config = DummyOrgConfig(
1643
+ {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
1644
+ )
1645
+
1646
+ expected_error_message = "The lookup Event is not a valid lookup"
1647
+
1648
+ with pytest.raises(BulkDataException) as e:
1649
+ _infer_and_validate_lookups(
1650
+ mapping=mapping, sf=org_config.salesforce_client
1651
+ )
1652
+ assert "One or more relationship errors blocked the operation" in str(
1653
+ e.value
1654
+ )
1655
+ error_logs = [
1656
+ record.message for record in caplog.records if record.levelname == "ERROR"
1657
+ ]
1658
+ assert any(expected_error_message in error_log for error_log in error_logs)