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,1153 @@
1
+ import re
2
+ import typing as T
3
+ from collections import Counter
4
+ from contextlib import contextmanager
5
+ from itertools import cycle
6
+ from pathlib import Path
7
+ from tempfile import TemporaryDirectory
8
+ from threading import Lock, Thread
9
+ from unittest import mock
10
+
11
+ import pytest
12
+ import yaml
13
+ from sqlalchemy import MetaData, create_engine
14
+
15
+ from cumulusci.core import exceptions as exc
16
+ from cumulusci.core.config import OrgConfig
17
+ from cumulusci.tasks.bulkdata.snowfakery import (
18
+ RunningTotals,
19
+ Snowfakery,
20
+ SnowfakeryWorkingDirectory,
21
+ )
22
+ from cumulusci.tasks.bulkdata.tests.integration_test_utils import ensure_accounts
23
+ from cumulusci.tasks.bulkdata.tests.utils import _make_task
24
+ from cumulusci.tasks.salesforce.BaseSalesforceApiTask import BaseSalesforceApiTask
25
+ from cumulusci.tests.util import DummyKeychain, DummyOrgConfig
26
+ from cumulusci.utils.parallel.task_worker_queues.tests.test_parallel_worker import (
27
+ DelaySpawner,
28
+ )
29
+
30
+ ensure_accounts = ensure_accounts # fixes 4 lint errors at once. Don't hate the player, hate the game.
31
+
32
+ simple_salesforce_yaml = (
33
+ Path(__file__).parent / "snowfakery/simple_snowfakery.recipe.yml"
34
+ )
35
+ sample_yaml = Path(__file__).parent / "snowfakery/gen_npsp_standard_objects.recipe.yml"
36
+ query_yaml = Path(__file__).parent / "snowfakery/query_snowfakery.recipe.yml"
37
+
38
+ original_refresh_token = OrgConfig.refresh_oauth_token
39
+
40
+ FAKE_LOAD_RESULTS = (
41
+ {
42
+ "Insert Account": {
43
+ "sobject": "Account",
44
+ "record_type": None,
45
+ "status": "Success",
46
+ "records_processed": 2,
47
+ "total_row_errors": 0,
48
+ },
49
+ "Insert Contact": {
50
+ "sobject": "Contact",
51
+ "record_type": None,
52
+ "status": "Success",
53
+ "records_processed": 2,
54
+ "total_row_errors": 0,
55
+ },
56
+ },
57
+ {
58
+ "Insert Account": {
59
+ "sobject": "Account",
60
+ "record_type": None,
61
+ "status": "Success",
62
+ "records_processed": 3,
63
+ "total_row_errors": 0,
64
+ },
65
+ "Insert Contact": {
66
+ "sobject": "Contact",
67
+ "record_type": None,
68
+ "status": "Success",
69
+ "records_processed": 3,
70
+ "total_row_errors": 0,
71
+ },
72
+ },
73
+ )
74
+
75
+
76
+ def table_values(connection, table):
77
+ query = f"select * from {table.name}"
78
+ values = [val for val in connection.execute(query)]
79
+ return values
80
+
81
+
82
+ class FakeLoadData(BaseSalesforceApiTask):
83
+ """Simulates load results without doing a real load."""
84
+
85
+ # these are all used as mutable class variables
86
+ mock_calls: list # similar to how a mock.Mock() object works
87
+ fake_return_values: T.Iterator
88
+ fake_exception_on_request = -1
89
+ lock = Lock()
90
+
91
+ # Manipulating "self" from a mock side-effect is a challenge.
92
+ # So we need a "real function"
93
+ def __call__(self, *args, **kwargs):
94
+ """Like the __call__ of _run_task, but also capture calls
95
+ in a normal mock_values structure."""
96
+
97
+ with self.lock: # the code below looks thread-safe but better safe than sorry
98
+
99
+ # tasks usually aren't called twice after being instantiated
100
+ # that would usually be a bug.
101
+ assert self not in self.mock_calls
102
+ self.__class__.mock_calls.append(self)
103
+
104
+ if (
105
+ len(self.__class__.mock_calls)
106
+ == self.__class__.fake_exception_on_request
107
+ ):
108
+ raise AssertionError("You asked me to raise an exception")
109
+
110
+ # get the values that the Snowfakery task asked us to load and
111
+ # remember them for later inspection.
112
+ self.values_loaded = db_values_from_db_url(self.options["database_url"])
113
+
114
+ # TODO:
115
+ #
116
+ # Parse the mapping and then count matching objects to return
117
+ # more realistic values.
118
+ # return a fake return value so Snowfakery loader doesn't get confused
119
+ self.return_values = {"step_results": next(self.fake_return_values)}
120
+
121
+ # using mutable class variables is not something I would usually do
122
+ # because it is not thread safe, but the test intrinsically uses
123
+ # threads and therefore is not thread safe in general.
124
+ #
125
+ # Furthermore, attempts to use a closure instead of mutable class
126
+ # variables just doesn't work because of how Snowfakery instantiates
127
+ # tasks in sub-threads.
128
+ @classmethod
129
+ def reset(cls, fake_exception_on_request=-1):
130
+ cls.mock_calls = []
131
+ cls.fake_return_values = cycle(iter(FAKE_LOAD_RESULTS))
132
+ cls.fake_exception_on_request = fake_exception_on_request
133
+
134
+
135
+ def db_values_from_db_url(database_url):
136
+ engine = create_engine(database_url)
137
+ metadata = MetaData(engine)
138
+ metadata.reflect()
139
+
140
+ with engine.connect() as connection:
141
+ values = {
142
+ table_name: table_values(connection, table)
143
+ for table_name, table in metadata.tables.items()
144
+ if table_name[-6:] != "sf_ids"
145
+ }
146
+ return values
147
+
148
+
149
+ @pytest.fixture
150
+ def mock_load_data(
151
+ request,
152
+ threads_instead_of_processes, # mock patches wouldn't be inherited by child processs
153
+ ):
154
+
155
+ fake_load_data = FakeLoadData
156
+ with mock.patch(
157
+ "cumulusci.tasks.bulkdata.generate_and_load_data.LoadData", fake_load_data
158
+ ), mock.patch(
159
+ "cumulusci.tasks.bulkdata.snowfakery_utils.queue_manager.LoadData",
160
+ fake_load_data,
161
+ ):
162
+ fake_load_data.reset()
163
+
164
+ yield fake_load_data
165
+ fake_load_data.reset()
166
+
167
+
168
+ @pytest.fixture
169
+ def threads_instead_of_processes(request):
170
+ with mock.patch(
171
+ "cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
172
+ wraps=Thread,
173
+ ) as t:
174
+ yield t
175
+
176
+
177
+ @pytest.fixture
178
+ def fake_processes_and_threads(request):
179
+ class FakeProcessManager:
180
+ def __init__(self):
181
+ self.processes = []
182
+
183
+ def __call__(self, target, args, daemon):
184
+ res = self.process_handler(target, args, daemon, index=len(self.processes))
185
+ self.processes.append(res)
186
+ return res
187
+
188
+ process_manager = FakeProcessManager()
189
+
190
+ with mock.patch(
191
+ "cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Thread",
192
+ process_manager,
193
+ ), mock.patch(
194
+ "cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
195
+ process_manager,
196
+ ):
197
+ yield process_manager
198
+
199
+
200
+ @pytest.fixture
201
+ def snowfakery(request, create_task):
202
+ def snowfakery(**kwargs):
203
+ return create_task(Snowfakery, kwargs)
204
+
205
+ return snowfakery
206
+
207
+
208
+ @contextmanager
209
+ def temporary_file_path(filename):
210
+ with TemporaryDirectory() as tmpdirname:
211
+ path = Path(tmpdirname) / filename
212
+ yield path
213
+
214
+
215
+ class SnowfakeryTaskResults(T.NamedTuple):
216
+ """Results from a Snowfakery data generation process"""
217
+
218
+ task: Snowfakery # The task, so we can inspect its return_values
219
+ working_dir: Path # The working directory, to look at mapping files, DB files, etc.
220
+ values_loaded: T.List[T.Dict]
221
+
222
+
223
+ @pytest.fixture()
224
+ def run_snowfakery_and_inspect_mapping(
225
+ run_snowfakery_and_yield_results,
226
+ ):
227
+ """Run Snowfakery with some defaulted or overriden options.
228
+ Yield a mapping file for inspection that it was the right file.
229
+
230
+ Defaults are same as run_snowfakery_and_yield_results.
231
+ """
232
+
233
+ def _run_snowfakery_and_inspect_mapping(**options):
234
+ with run_snowfakery_and_yield_results(**options) as results:
235
+ return get_mapping_from_snowfakery_task_results(results)
236
+
237
+ return _run_snowfakery_and_inspect_mapping
238
+
239
+
240
+ def get_mapping_from_snowfakery_task_results(results: SnowfakeryTaskResults):
241
+ """Find the shared mapping file and return it."""
242
+ template_dir = SnowfakeryWorkingDirectory(results.working_dir / "template_1/")
243
+ temp_mapping = template_dir.mapping_file
244
+ with open(temp_mapping) as f:
245
+ mapping = yaml.safe_load(f)
246
+
247
+ other_mapping = Path(
248
+ str(temp_mapping).replace("template_1", "data_load_outbox/1_1")
249
+ )
250
+ if other_mapping.exists():
251
+ # check that it's truly shared
252
+ assert temp_mapping.read_text() == other_mapping.read_text()
253
+ return mapping
254
+
255
+
256
+ def get_record_counts_from_snowfakery_results(
257
+ results: SnowfakeryTaskResults,
258
+ ) -> Counter:
259
+ """Collate the record counts from Snowfakery outbox directories.
260
+ Note that records created by the initial, just_once seeding flow are not
261
+ counted because they are deleted. If you need every single result, you
262
+ should probably use return_values instead. (but you may need to implement it)"""
263
+
264
+ rollups = Counter()
265
+ # when there is more than one channel, the directory structure is deeper
266
+ channeled_outboxes = tuple(results.working_dir.glob("*/data_load_outbox/*"))
267
+ regular_outboxes = tuple(results.working_dir.glob("data_load_outbox/*"))
268
+
269
+ assert bool(regular_outboxes) ^ bool(
270
+ channeled_outboxes
271
+ ), f"One of regular_outboxes or channeled_outboxes should be available: {channeled_outboxes}, {regular_outboxes}"
272
+ outboxes = tuple(channeled_outboxes) + tuple(regular_outboxes)
273
+ for subdir in outboxes:
274
+ record_counts = SnowfakeryWorkingDirectory(subdir).get_record_counts()
275
+ rollups.update(record_counts)
276
+
277
+ return rollups
278
+
279
+
280
+ @pytest.fixture()
281
+ def run_snowfakery_and_yield_results(snowfakery, mock_load_data):
282
+ @contextmanager
283
+ def _run_snowfakery_and_inspect_mapping_and_example_records(**options):
284
+ with TemporaryDirectory() as workingdir:
285
+ workingdir = Path(workingdir) / "tempdir"
286
+ task = snowfakery(
287
+ working_directory=workingdir,
288
+ **options,
289
+ )
290
+ task()
291
+ values_loaded = [
292
+ mock_call.values_loaded for mock_call in mock_load_data.mock_calls
293
+ ]
294
+ yield SnowfakeryTaskResults(task, workingdir, values_loaded)
295
+
296
+ return _run_snowfakery_and_inspect_mapping_and_example_records
297
+
298
+
299
+ class TestSnowfakery:
300
+ def test_no_options(self):
301
+ with pytest.raises(exc.TaskOptionsError, match="recipe"):
302
+ _make_task(Snowfakery, {})
303
+
304
+ @mock.patch(
305
+ "cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
306
+ )
307
+ def test_simple_snowfakery(self, Process, mock_load_data, create_task):
308
+ task = create_task(
309
+ Snowfakery,
310
+ {
311
+ "recipe": sample_yaml,
312
+ },
313
+ )
314
+ task()
315
+ # should not be called for a simple one-rep load
316
+ assert not Process.mock_calls
317
+
318
+ @mock.patch(
319
+ "cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
320
+ )
321
+ @pytest.mark.vcr()
322
+ def test_snowfakery_query_salesforce(self, Process, mock_load_data, create_task):
323
+ task = create_task(
324
+ Snowfakery,
325
+ {
326
+ "recipe": query_yaml,
327
+ },
328
+ )
329
+ task()
330
+ assert mock_load_data.mock_calls
331
+ # should not be called for a simple one-rep load
332
+ assert not Process.mock_calls
333
+
334
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
335
+ def test_small(
336
+ self, mock_load_data, threads_instead_of_processes, create_task_fixture
337
+ ):
338
+ task = create_task_fixture(
339
+ Snowfakery,
340
+ {
341
+ "recipe": sample_yaml,
342
+ "run_until_recipe_repeated": "7",
343
+ "drop_missing_schema": True,
344
+ },
345
+ )
346
+ task()
347
+ # Batch size was 3, so 7 records takes
348
+ # one initial batch plus two parallel batches
349
+ assert len(mock_load_data.mock_calls) == 3, mock_load_data.mock_calls
350
+ # One should be in a sub-process/thread
351
+ assert len(threads_instead_of_processes.mock_calls) == 2
352
+ for call in mock_load_data.mock_calls:
353
+ assert call.task_config.config["options"]["drop_missing_schema"] is True
354
+
355
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
356
+ def test_multi_part(
357
+ self, threads_instead_of_processes, mock_load_data, create_task_fixture
358
+ ):
359
+ task = create_task_fixture(
360
+ Snowfakery,
361
+ {"recipe": sample_yaml, "run_until_recipe_repeated": 15},
362
+ )
363
+ task()
364
+ assert (
365
+ len(mock_load_data.mock_calls) > 3
366
+ ) # depends on the details of the tuning
367
+ assert (
368
+ len(threads_instead_of_processes.mock_calls)
369
+ == len(mock_load_data.mock_calls) - 1
370
+ )
371
+ for call in mock_load_data.mock_calls:
372
+ assert call.task_config.config["options"]["drop_missing_schema"] is False
373
+
374
+ @mock.patch(
375
+ "cumulusci.utils.parallel.task_worker_queues.parallel_worker_queue.WorkerQueue.Process",
376
+ )
377
+ def test_run_until_loaded(
378
+ self, create_subprocess, mock_load_data, create_task_fixture
379
+ ):
380
+ task = create_task_fixture(
381
+ Snowfakery,
382
+ {"recipe": sample_yaml, "run_until_records_loaded": "Account:1"},
383
+ )
384
+ task()
385
+ assert mock_load_data.mock_calls
386
+ # should not be called for a simple one-rep load
387
+ assert not create_subprocess.mock_calls
388
+
389
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
390
+ def test_run_until_loaded_2_parts(
391
+ self, threads_instead_of_processes, mock_load_data, create_task_fixture
392
+ ):
393
+ task = create_task_fixture(
394
+ Snowfakery,
395
+ {"recipe": sample_yaml, "run_until_records_loaded": "Account:6"},
396
+ )
397
+ task()
398
+ assert len(mock_load_data.mock_calls) == 2
399
+ assert len(threads_instead_of_processes.mock_calls) == 1
400
+
401
+ # There was previously a failed attempt at testing the connected app here.
402
+ # Could try again after Snowfakery 2.0 launch.
403
+ # https://github.com/SFDO-Tooling/CumulusCI/blob/c7e0d7552394b3ac268cb373ffb24b72b5c059f3/cumulusci/tasks/bulkdata/tests/test_snowfakery.py#L165-L197https://github.com/SFDO-Tooling/CumulusCI/blob/c7e0d7552394b3ac268cb373ffb24b72b5c059f3/cumulusci/tasks/bulkdata/tests/test_snowfakery.py#L165-L197
404
+
405
+ @pytest.mark.vcr()
406
+ def test_run_until_records_in_org__none_needed(
407
+ self, threads_instead_of_processes, mock_load_data, create_task, ensure_accounts
408
+ ):
409
+ with ensure_accounts(6):
410
+ task = create_task(
411
+ Snowfakery,
412
+ {"recipe": sample_yaml, "run_until_records_in_org": "Account:6"},
413
+ )
414
+ task()
415
+ assert len(mock_load_data.mock_calls) == 0, mock_load_data.mock_calls
416
+ assert (
417
+ len(threads_instead_of_processes.mock_calls) == 0
418
+ ), threads_instead_of_processes.mock_calls
419
+
420
+ @pytest.mark.vcr()
421
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 5)
422
+ def test_run_until_records_in_org__one_needed(
423
+ self,
424
+ sf,
425
+ threads_instead_of_processes,
426
+ mock_load_data,
427
+ create_task,
428
+ ensure_accounts,
429
+ ):
430
+ with ensure_accounts(10):
431
+ # org reports 10 records in org
432
+ # so we only need 6 more.
433
+ # That will be one "initial" batch of 1 plus one "parallel" batch of 5
434
+ task = create_task(
435
+ Snowfakery,
436
+ {"recipe": sample_yaml, "run_until_records_in_org": "Account:16"},
437
+ )
438
+ task.logger = mock.Mock()
439
+ task()
440
+ assert len(mock_load_data.mock_calls) == 2, mock_load_data.mock_calls
441
+ assert len(threads_instead_of_processes.mock_calls) == 1
442
+
443
+ @pytest.mark.vcr()
444
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
445
+ def test_run_until_records_in_org__multiple_needed(
446
+ self,
447
+ threads_instead_of_processes,
448
+ mock_load_data,
449
+ snowfakery,
450
+ ensure_accounts,
451
+ ):
452
+ with ensure_accounts(10):
453
+ task = snowfakery(recipe=sample_yaml, run_until_records_in_org="Account:16")
454
+ task()
455
+
456
+ assert len(mock_load_data.mock_calls) == 2, mock_load_data.mock_calls
457
+ assert (
458
+ len(threads_instead_of_processes.mock_calls) == 1
459
+ ), threads_instead_of_processes.mock_calls
460
+
461
+ def test_inaccessible_generator_yaml(self, snowfakery):
462
+ with pytest.raises(exc.TaskOptionsError, match="recipe"):
463
+ task = snowfakery(
464
+ recipe=sample_yaml / "junk",
465
+ )
466
+ task()
467
+
468
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.get_debug_mode", lambda: True)
469
+ @mock.patch("psutil.cpu_count", lambda logical: 11)
470
+ def test_snowfakery_debug_mode_and_cpu_count(self, snowfakery, mock_load_data):
471
+ task = snowfakery(recipe=sample_yaml, run_until_recipe_repeated="5")
472
+ with mock.patch.object(task, "logger") as logger:
473
+ task()
474
+ assert "Using 11 workers" in str(logger.mock_calls)
475
+
476
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
477
+ def test_record_count(self, snowfakery, mock_load_data):
478
+ task = snowfakery(recipe="datasets/recipe.yml", run_until_recipe_repeated="4")
479
+ with mock.patch.object(task, "logger") as logger, mock.patch.object(
480
+ task.project_config, "keychain", DummyKeychain()
481
+ ) as keychain:
482
+
483
+ def get_org(username):
484
+ return DummyOrgConfig(
485
+ config={"keychain": keychain, "username": username}
486
+ )
487
+
488
+ keychain.get_org = mock.Mock(wraps=get_org)
489
+ task()
490
+ mock_calls_as_string = str(logger.mock_calls)
491
+ # note that load_data is mocked so these values are based on FAKE_LOAD_RESULTS
492
+ # See also: the TODO in FakeLoadData.__call__
493
+ assert "Account: 5 successes" in mock_calls_as_string, mock_calls_as_string[
494
+ -500:
495
+ ]
496
+ assert "Contact: 5 successes" in mock_calls_as_string, mock_calls_as_string[
497
+ -500:
498
+ ]
499
+
500
+ def test_run_until_wrong_format(self, snowfakery):
501
+ with pytest.raises(exc.TaskOptionsError, match="Ten"):
502
+ task = snowfakery(
503
+ recipe=sample_yaml, run_until_records_loaded="Account:Ten"
504
+ )
505
+ task()
506
+
507
+ def test_run_until_wrong_format__2(self, snowfakery):
508
+ with pytest.raises(exc.TaskOptionsError, match="Ten"):
509
+ task = snowfakery(
510
+ recipe=sample_yaml, run_until_records_loaded="Account_Ten"
511
+ )
512
+ task()
513
+
514
+ def test_run_reps_wrong_format(self, snowfakery):
515
+ with pytest.raises(exc.TaskOptionsError, match="Ten"):
516
+ task = snowfakery(recipe=sample_yaml, run_until_recipe_repeated="Ten")
517
+ task()
518
+
519
+ def test_run_until_conflicting_params(self, snowfakery):
520
+ with pytest.raises(exc.TaskOptionsError, match="only one of"):
521
+ task = snowfakery(
522
+ recipe=sample_yaml,
523
+ run_until_records_loaded="Account_Ten",
524
+ run_until_recipe_repeated="1",
525
+ )
526
+ task()
527
+
528
+ def test_working_directory(self, snowfakery, mock_load_data):
529
+ with TemporaryDirectory() as t:
530
+ working_directory = Path(t) / "junkdir"
531
+ task = snowfakery(
532
+ recipe=sample_yaml,
533
+ run_until_recipe_repeated="1",
534
+ working_directory=str(working_directory),
535
+ )
536
+ task()
537
+ assert (working_directory / "data_load_outbox").exists()
538
+
539
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 1)
540
+ def xxx__test_failures_in_subprocesses__last_batch(
541
+ self, snowfakery, mock_load_data, fake_processes_and_threads
542
+ ):
543
+ class FakeProcess(DelaySpawner):
544
+ def __init__(self, target, args, daemon, index):
545
+ super().__init__(target, args, daemon)
546
+ self.counter = 0
547
+ self.task_class = args[0]["task_class"]
548
+ self.index = index
549
+ try:
550
+ self._finish()
551
+ except AssertionError:
552
+ pass
553
+
554
+ def is_alive(self):
555
+ print("Alive?", self.task_class, self.index, self.counter, self)
556
+ self.counter += 1
557
+ if self.counter > 3:
558
+ return False
559
+ return True
560
+
561
+ fake_processes_and_threads.process_handler = FakeProcess
562
+
563
+ class LoadDataSucceedsOnceThenFails:
564
+ count = 0
565
+
566
+ def __call__(self, *args, **kwargs):
567
+ self.count += 1
568
+ if self.count > 1:
569
+ raise AssertionError("XYZZY")
570
+
571
+ mock_load_data.side_effect = LoadDataSucceedsOnceThenFails()
572
+
573
+ task = snowfakery(
574
+ recipe=sample_yaml,
575
+ run_until_records_loaded="Account:10",
576
+ num_processes=3, # todo: test this is enforced
577
+ )
578
+ with mock.patch.object(task, "logger") as logger:
579
+ with pytest.raises(exc.BulkDataException):
580
+ task()
581
+ assert "XYZZY" in str(logger.mock_calls)
582
+
583
+ def test_running_totals_repr(self):
584
+ r = RunningTotals()
585
+ r.errors = 12
586
+ r.successes = 11
587
+ assert "11" in repr(r)
588
+
589
+ def test_generate_mapping_file__loadfile__inferred(
590
+ self, run_snowfakery_and_inspect_mapping
591
+ ):
592
+
593
+ mapping = run_snowfakery_and_inspect_mapping(
594
+ recipe=simple_salesforce_yaml,
595
+ run_until_recipe_repeated=2,
596
+ )
597
+
598
+ assert mapping["Insert Account"]["api"] == "bulk"
599
+ assert mapping["Insert Contact"].get("bulk_mode") is None
600
+ assert list(mapping.keys()) == ["Insert Account", "Insert Contact"]
601
+
602
+ def test_generate_mapping_file__loadfile__overridden(
603
+ self, run_snowfakery_and_inspect_mapping
604
+ ):
605
+ loading_rules = str(simple_salesforce_yaml).replace(
606
+ ".recipe.yml", "_2.load.yml"
607
+ )
608
+ mapping = run_snowfakery_and_inspect_mapping(
609
+ recipe=simple_salesforce_yaml,
610
+ loading_rules=str(loading_rules),
611
+ run_until_recipe_repeated=2,
612
+ )
613
+
614
+ assert mapping["Insert Account"].get("api") is None
615
+ assert mapping["Insert Contact"]["bulk_mode"].lower() == "parallel"
616
+ assert list(mapping.keys()) == ["Insert Contact", "Insert Account"]
617
+
618
+ def test_generate_mapping_file__loadfile_multiple_files(
619
+ self, run_snowfakery_and_inspect_mapping
620
+ ):
621
+ loading_rules = (
622
+ str(simple_salesforce_yaml).replace(".recipe.yml", "_2.load.yml")
623
+ + ","
624
+ + str(simple_salesforce_yaml).replace(".recipe.yml", ".load.yml")
625
+ )
626
+ mapping = run_snowfakery_and_inspect_mapping(
627
+ recipe=simple_salesforce_yaml,
628
+ loading_rules=str(loading_rules),
629
+ run_until_recipe_repeated=2,
630
+ )
631
+
632
+ assert mapping["Insert Account"]["api"] == "bulk"
633
+ assert mapping["Insert Contact"]["bulk_mode"].lower() == "parallel"
634
+ assert list(mapping.keys()) == ["Insert Contact", "Insert Account"]
635
+
636
+ @mock.patch(
637
+ "cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 1
638
+ ) # force multi-step load
639
+ def test_options(
640
+ self,
641
+ mock_load_data,
642
+ run_snowfakery_and_yield_results,
643
+ ):
644
+ options_yaml = str(sample_yaml).replace(
645
+ "gen_npsp_standard_objects.recipe.yml", "options.recipe.yml"
646
+ )
647
+ with run_snowfakery_and_yield_results(
648
+ recipe=options_yaml,
649
+ recipe_options="row_count:7,account_name:FakeAccountName",
650
+ run_until_recipe_repeated=2,
651
+ ) as results:
652
+ record_counts = get_record_counts_from_snowfakery_results(results)
653
+ assert (
654
+ results.values_loaded[0]["Account"][0]["name"]
655
+ == "Account FakeAccountName"
656
+ )
657
+ assert record_counts["Account"] == 7, record_counts["Account"]
658
+
659
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 3)
660
+ def test_multi_part_uniqueness(self, mock_load_data, create_task_fixture):
661
+ task = create_task_fixture(
662
+ Snowfakery,
663
+ {
664
+ "recipe": Path(__file__).parent / "snowfakery/unique_values.recipe.yml",
665
+ "run_until_recipe_repeated": 15,
666
+ },
667
+ )
668
+ task()
669
+ all_data_load_inputs = mock_load_data.mock_calls
670
+ all_rows = [
671
+ task_instance.values_loaded["blah"]
672
+ for task_instance in all_data_load_inputs
673
+ ]
674
+
675
+ unique_values = [row.value for batchrows in all_rows for row in batchrows]
676
+ assert len(mock_load_data.mock_calls) == 6, len(mock_load_data.mock_calls)
677
+ assert len(unique_values) == 30, len(unique_values)
678
+ assert len(set(unique_values)) == 30, unique_values
679
+
680
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
681
+ def test_two_channels(self, mock_load_data, create_task):
682
+ task = create_task(
683
+ Snowfakery,
684
+ {
685
+ "recipe": Path(__file__).parent
686
+ / "snowfakery/simple_snowfakery_channels.recipe.yml",
687
+ "run_until_recipe_repeated": 15,
688
+ "recipe_options": {"xyzzy": "Nothing happens", "some_number": 42},
689
+ },
690
+ )
691
+ with mock.patch.object(
692
+ task.project_config, "keychain", DummyKeychain()
693
+ ) as keychain:
694
+
695
+ def get_org(username):
696
+ return DummyOrgConfig(
697
+ config={"keychain": keychain, "username": username}
698
+ )
699
+
700
+ keychain.get_org = mock.Mock(wraps=get_org)
701
+ task()
702
+ assert keychain.get_org.mock_calls
703
+ assert keychain.get_org.call_args_list
704
+ assert keychain.get_org.call_args_list == [
705
+ (("channeltest",),),
706
+ (("channeltest-b",),),
707
+ (("channeltest-c",),),
708
+ (("Account",),),
709
+ ], keychain.get_org.call_args_list
710
+
711
+ all_data_load_inputs = mock_load_data.mock_calls
712
+ all_data_load_inputs = sorted(
713
+ all_data_load_inputs,
714
+ key=lambda task_instance: task_instance.org_config.username,
715
+ )
716
+ usernames_values = [
717
+ (task_instance.org_config.username, task_instance.values_loaded)
718
+ for task_instance in all_data_load_inputs
719
+ ]
720
+ count_loads = Counter(username for username, _ in usernames_values)
721
+ assert count_loads.keys() == {
722
+ "channeltest",
723
+ "channeltest-b",
724
+ "channeltest-c",
725
+ "Account",
726
+ }
727
+
728
+ # depends on threading. :(
729
+ for value in count_loads.values():
730
+ assert 1 <= value <= 4, value
731
+ assert sum(count_loads.values()) == 8
732
+
733
+ first_row_values = next(
734
+ value["Account"]
735
+ for username, value in usernames_values
736
+ if username == "channeltest"
737
+ )
738
+ assert len(first_row_values) == 1, len(first_row_values)
739
+ for username, values in usernames_values:
740
+ accounts = values["Account"]
741
+ if values["Account"] != first_row_values:
742
+ assert len(accounts) == 2, (values, first_row_values)
743
+ for account in accounts:
744
+ assert int(account.some_number) == 42
745
+ assert username in account.name, (username, account.name)
746
+
747
+ assert sum(len(v["Account"]) for _, v in usernames_values) == 15, sum(
748
+ len(v) for _, v in usernames_values
749
+ )
750
+
751
+ def test_channels_cli_options_conflict(self, create_task):
752
+ task = create_task(
753
+ Snowfakery,
754
+ {
755
+ "recipe": Path(__file__).parent
756
+ / "snowfakery/simple_snowfakery_channels.recipe.yml",
757
+ "run_until_recipe_repeated": 15,
758
+ "recipe_options": {"xyzzy": "Nothing happens", "some_number": 37},
759
+ },
760
+ )
761
+ with pytest.raises(exc.TaskOptionsError) as e, mock.patch.object(
762
+ task.project_config, "keychain", DummyKeychain()
763
+ ) as keychain:
764
+
765
+ def get_org(username):
766
+ return DummyOrgConfig(
767
+ config={"keychain": keychain, "username": username}
768
+ )
769
+
770
+ keychain.get_org = mock.Mock(wraps=get_org)
771
+ task()
772
+ assert "conflict" in str(e.value)
773
+ assert "some_number" in str(e.value)
774
+
775
+ @mock.patch(
776
+ "cumulusci.tasks.bulkdata.snowfakery.get_debug_mode", lambda: True
777
+ ) # for coverage
778
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
779
+ def test_explicit_channel_declarations(self, mock_load_data, create_task):
780
+ task = create_task(
781
+ Snowfakery,
782
+ {
783
+ "recipe": Path(__file__).parent
784
+ / "snowfakery/simple_snowfakery.recipe.yml",
785
+ "run_until_recipe_repeated": 15,
786
+ "loading_rules": Path(__file__).parent
787
+ / "snowfakery/simple_snowfakery_channels.load.yml",
788
+ },
789
+ )
790
+ with pytest.warns(UserWarning), mock.patch.object(
791
+ task.project_config, "keychain", DummyKeychain()
792
+ ) as keychain:
793
+
794
+ def get_org(username):
795
+ return DummyOrgConfig(
796
+ config={"keychain": keychain, "username": username}
797
+ )
798
+
799
+ keychain.get_org = mock.Mock(wraps=get_org)
800
+ task()
801
+ assert keychain.get_org.mock_calls
802
+ assert keychain.get_org.call_args_list
803
+ assert keychain.get_org.call_args_list == [
804
+ (("channeltest",),),
805
+ (("channeltest-b",),),
806
+ (("channeltest-c",),),
807
+ (("Account",),),
808
+ ], keychain.get_org.call_args_list
809
+
810
+ all_data_load_inputs = mock_load_data.mock_calls
811
+ all_data_load_inputs = sorted(
812
+ all_data_load_inputs,
813
+ key=lambda task_instance: task_instance.org_config.username,
814
+ )
815
+ usernames_values = [
816
+ (task_instance.org_config.username, task_instance.values_loaded)
817
+ for task_instance in all_data_load_inputs
818
+ ]
819
+ count_loads = Counter(username for username, _ in usernames_values)
820
+ assert count_loads.keys() == {
821
+ "channeltest",
822
+ "channeltest-b",
823
+ "channeltest-c",
824
+ "Account",
825
+ }
826
+
827
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
828
+ def test_serial_mode(self, mock_load_data, create_task):
829
+ task = create_task(
830
+ Snowfakery,
831
+ {
832
+ "recipe": Path(__file__).parent
833
+ / "snowfakery/simple_snowfakery.recipe.yml",
834
+ "run_until_recipe_repeated": 15,
835
+ "bulk_mode": "Serial",
836
+ },
837
+ )
838
+ with mock.patch.object(
839
+ task.project_config, "keychain", DummyKeychain()
840
+ ) as keychain:
841
+
842
+ def get_org(username):
843
+ return DummyOrgConfig(
844
+ config={"keychain": keychain, "username": username}
845
+ )
846
+
847
+ keychain.get_org = mock.Mock(wraps=get_org)
848
+ task.logger = mock.Mock()
849
+ task()
850
+ for data_load_fake in mock_load_data.mock_calls:
851
+ assert data_load_fake.options["bulk_mode"] == "Serial"
852
+ pattern = r"Inprogress Loader Jobs: (\d+)"
853
+ loader_counts = re.findall(pattern, str(task.logger.mock_calls))
854
+ assert loader_counts, loader_counts
855
+ assert 0 <= all(int(count) <= 1 for count in loader_counts), loader_counts
856
+
857
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
858
+ def test_bulk_mode_error(self, create_task, mock_load_data):
859
+ with pytest.raises(exc.TaskOptionsError):
860
+ task = create_task(
861
+ Snowfakery,
862
+ {
863
+ "recipe": Path(__file__).parent
864
+ / "snowfakery/simple_snowfakery.recipe.yml",
865
+ "bulk_mode": "XYZZY",
866
+ },
867
+ )
868
+ task()
869
+
870
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
871
+ def test_too_many_channel_declarations(self, mock_load_data, create_task):
872
+ task = create_task(
873
+ Snowfakery,
874
+ {
875
+ "recipe": Path(__file__).parent
876
+ / "snowfakery/simple_snowfakery_channels.recipe.yml",
877
+ "run_until_recipe_repeated": 15,
878
+ "recipe_options": {"xyzzy": "Nothing happens", "some_number": 42},
879
+ "loading_rules": Path(__file__).parent
880
+ / "snowfakery/simple_snowfakery_channels_2.load.yml",
881
+ },
882
+ )
883
+ with pytest.raises(exc.TaskOptionsError), mock.patch.object(
884
+ task.project_config, "keychain", DummyKeychain()
885
+ ) as keychain:
886
+
887
+ def get_org(username):
888
+ return DummyOrgConfig(
889
+ config={"keychain": keychain, "username": username}
890
+ )
891
+
892
+ keychain.get_org = mock.Mock(wraps=get_org)
893
+ task()
894
+
895
+ @pytest.mark.skip() # TODO: make handling of errors more predictable and re-enable
896
+ @mock.patch("cumulusci.tasks.bulkdata.snowfakery.MIN_PORTION_SIZE", 2)
897
+ def test_error_handling_in_channels(self, mock_load_data, create_task):
898
+ task = create_task(
899
+ Snowfakery,
900
+ {
901
+ "recipe": Path(__file__).parent
902
+ / "snowfakery/simple_snowfakery.recipe.yml",
903
+ "run_until_recipe_repeated": 15,
904
+ "loading_rules": Path(__file__).parent
905
+ / "snowfakery/simple_snowfakery_channels.load.yml",
906
+ },
907
+ )
908
+ with mock.patch.object(
909
+ task.project_config, "keychain", DummyKeychain()
910
+ ) as keychain:
911
+
912
+ def get_org(username):
913
+ return DummyOrgConfig(
914
+ config={"keychain": keychain, "username": username}
915
+ )
916
+
917
+ keychain.get_org = mock.Mock(wraps=get_org)
918
+ with pytest.raises(exc.BulkDataException):
919
+ mock_load_data.reset(fake_exception_on_request=3)
920
+ task()
921
+
922
+ @pytest.mark.vcr()
923
+ @pytest.mark.skip()
924
+ def test_snowfakery_upsert(self, create_task, sf, run_code_without_recording):
925
+ task = create_task(
926
+ Snowfakery,
927
+ {
928
+ "recipe": Path(__file__).parent / "snowfakery/upsert.recipe.yml",
929
+ },
930
+ )
931
+
932
+ def assert_bluth_name(name):
933
+ data = sf.query(
934
+ "select FirstName from Contact where email='michael@bluth.com'"
935
+ )
936
+ assert data["records"][0]["FirstName"] == name
937
+
938
+ task()
939
+ run_code_without_recording(lambda: assert_bluth_name("Michael"))
940
+
941
+ task = create_task(
942
+ Snowfakery,
943
+ {
944
+ "recipe": Path(__file__).parent / "snowfakery/upsert_2.recipe.yml",
945
+ },
946
+ )
947
+
948
+ task()
949
+ run_code_without_recording(lambda: assert_bluth_name("Nichael"))
950
+
951
+ # def test_generate_mapping_file(self):
952
+ # with temporary_file_path("mapping.yml") as temp_mapping:
953
+ # with temp_sqlite_database_url() as database_url:
954
+ # task = _make_task(
955
+ # GenerateDataFromYaml,
956
+ # {
957
+ # "options": {
958
+ # "generator_yaml": sample_yaml,
959
+ # "database_url": database_url,
960
+ # "generate_mapping_file": temp_mapping,
961
+ # }
962
+ # },
963
+ # )
964
+ # task()
965
+ # mapping = yaml.safe_load(open(temp_mapping))
966
+ # assert mapping["Insert Account"]["fields"]
967
+
968
+ # def test_use_mapping_file(self):
969
+ # assert vanilla_mapping_file.exists()
970
+ # with temp_sqlite_database_url() as database_url:
971
+ # task = _make_task(
972
+ # GenerateDataFromYaml,
973
+ # {
974
+ # "options": {
975
+ # "generator_yaml": sample_yaml,
976
+ # "database_url": database_url,
977
+ # "mapping": vanilla_mapping_file,
978
+ # }
979
+ # },
980
+ # )
981
+ # task()
982
+ # self.assertRowsCreated(database_url)
983
+
984
+ # def test_num_records(self):
985
+ # with temp_sqlite_database_url() as database_url:
986
+ # task = _make_task(
987
+ # GenerateDataFromYaml,
988
+ # {
989
+ # "options": {
990
+ # "generator_yaml": simple_yaml,
991
+ # "database_url": database_url,
992
+ # }
993
+ # },
994
+ # )
995
+ # task()
996
+ # assert len(self.assertRowsCreated(database_url)) == 1, len(
997
+ # self.assertRowsCreated(database_url)
998
+ # )
999
+
1000
+ # @mock.patch(
1001
+ # "cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml.GenerateAndLoadDataFromYaml._dataload"
1002
+ # )
1003
+ # def test_simple_generate_and_load_with_numrecords(self, _dataload):
1004
+ # task = _make_task(s
1005
+ # GenerateAndLoadDataFromYaml,
1006
+ # {
1007
+ # "options": {
1008
+ # "generator_yaml": simple_yaml,
1009
+ # "num_records": 11,
1010
+ # "num_records_tablename": "Account",
1011
+ # }
1012
+ # },
1013
+ # )
1014
+ # task()
1015
+ # assert len(_dataload.mock_calls) == 1
1016
+
1017
+ # @mock.patch(
1018
+ # "cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml.GenerateAndLoadDataFromYaml._dataload"
1019
+ # )
1020
+ # def test_simple_generate_and_load(self, _dataload):
1021
+ # task = _make_task(
1022
+ # GenerateAndLoadDataFromYaml,
1023
+ # {
1024
+ # "options": {
1025
+ # "generator_yaml": simple_yaml,
1026
+ # "num_records": 11,
1027
+ # "num_records_tablename": "Account",
1028
+ # }
1029
+ # },
1030
+ # )
1031
+ # task()
1032
+ # assert len(_dataload.mock_calls) == 1
1033
+
1034
+ # @mock.patch("cumulusci.tasks.bulkdata.generate_from_yaml.generate_data")
1035
+ # def test_exception_handled_cleanly(self, generate_data):
1036
+ # generate_data.side_effect = AssertionError("Foo")
1037
+ # with pytest.raises(AssertionError) as e:
1038
+ # task = _make_task(
1039
+ # GenerateAndLoadDataFromYaml,
1040
+ # {
1041
+ # "options": {
1042
+ # "generator_yaml": simple_yaml,
1043
+ # "num_records": 11,
1044
+ # "num_records_tablename": "Account",
1045
+ # }
1046
+ # },
1047
+ # )
1048
+ # task()
1049
+ # assert "Foo" in str(e.value)
1050
+ # assert len(generate_data.mock_calls) == 1
1051
+
1052
+ # @mock.patch(
1053
+ # "cumulusci.tasks.bulkdata.generate_and_load_data_from_yaml.GenerateAndLoadDataFromYaml._dataload"
1054
+ # )
1055
+ # def test_batching(self, _dataload):
1056
+ # with temp_sqlite_database_url() as database_url:
1057
+ # task = _make_task(
1058
+ # GenerateAndLoadDataFromYaml,
1059
+ # {
1060
+ # "options": {
1061
+ # "generator_yaml": simple_yaml,
1062
+ # "num_records": 14,
1063
+ # "batch_size": 6,
1064
+ # "database_url": database_url,
1065
+ # "num_records_tablename": "Account",
1066
+ # "data_generation_task": "cumulusci.tasks.bulkdata.generate_from_yaml.GenerateDataFromYaml",
1067
+ # "reset_oids": False,
1068
+ # }
1069
+ # },
1070
+ # )
1071
+ # task()
1072
+ # assert len(_dataload.mock_calls) == 3
1073
+ # task = None # clean up db?
1074
+
1075
+ # engine = create_engine(database_url)
1076
+ # connection = engine.connect()
1077
+ # records = list(connection.execute("select * from Account"))
1078
+ # connection.close()
1079
+ # assert len(records) == 14 % 6 # leftovers
1080
+
1081
+ # def test_mismatched_options(self):
1082
+ # with pytest.raises(exc.exc.TaskOptionsError) as e:
1083
+ # task = _make_task(
1084
+ # GenerateDataFromYaml,
1085
+ # {"options": {"generator_yaml": sample_yaml, "num_records": 10}},
1086
+ # )
1087
+ # task()
1088
+ # assert "without num_records_tablename" in str(e.value)
1089
+
1090
+ # def test_with_nonexistent_continuation_file(self):
1091
+ # with pytest.raises(exc.TaskOptionsError) as e:
1092
+ # with temp_sqlite_database_url() as database_url:
1093
+ # task = _make_task(
1094
+ # GenerateDataFromYaml,
1095
+ # {
1096
+ # "options": {
1097
+ # "generator_yaml": sample_yaml,
1098
+ # "database_url": database_url,
1099
+ # "mapping": vanilla_mapping_file,
1100
+ # "continuation_file": "/tmp/foobar/baz/jazz/continuation.yml",
1101
+ # }
1102
+ # },
1103
+ # )
1104
+ # task()
1105
+ # rows = self.assertRowsCreated(database_url)
1106
+ # assert dict(rows[0])["id"] == 6
1107
+
1108
+ # assert "jazz" in str(e.value)
1109
+ # assert "does not exist" in str(e.value)
1110
+
1111
+ # def test_generate_continuation_file(self):
1112
+ # with temporary_file_path("cont.yml") as temp_continuation_file:
1113
+ # with temp_sqlite_database_url() as database_url:
1114
+ # task = _make_task(
1115
+ # GenerateDataFromYaml,
1116
+ # {
1117
+ # "options": {
1118
+ # "generator_yaml": sample_yaml,
1119
+ # "database_url": database_url,
1120
+ # "generate_continuation_file": temp_continuation_file,
1121
+ # }
1122
+ # },
1123
+ # )
1124
+ # task()
1125
+ # continuation_file = yaml.safe_load(open(temp_continuation_file))
1126
+ # assert continuation_file # internals of this file are not important to CumulusCI
1127
+
1128
+ # def _run_snowfakery_and_inspect_mapping(self, **options):
1129
+ # with temporary_file_path("mapping.yml") as temp_mapping:
1130
+ # with temp_sqlite_database_url() as database_url:
1131
+ # task = _make_task(
1132
+ # GenerateDataFromYaml,
1133
+ # {
1134
+ # "options": {
1135
+ # "database_url": database_url,
1136
+ # "generate_mapping_file": temp_mapping,
1137
+ # **options,
1138
+ # }
1139
+ # },
1140
+ # )
1141
+ # task()
1142
+ # with open(temp_mapping) as f:
1143
+ # mapping = yaml.safe_load(f)
1144
+ # return mapping
1145
+
1146
+ # def test_generate_mapping_file__loadfile_missing(self):
1147
+ # loading_rules = str(simple_snowfakery_yaml).replace(
1148
+ # ".recipe.yml", "_3.load.yml"
1149
+ # )
1150
+ # with pytest.raises(FileNotFoundError):
1151
+ # self._run_snowfakery_and_inspect_mapping(
1152
+ # generator_yaml=simple_snowfakery_yaml, loading_rules=str(loading_rules)
1153
+ # )