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,1895 @@
1
+ # -*- coding: utf-8 -*-
2
+ import json
3
+ import os
4
+ import pathlib
5
+ from pathlib import Path
6
+ from tempfile import TemporaryDirectory
7
+ from unittest import mock
8
+
9
+ import pytest
10
+ import responses
11
+ import yaml
12
+ from github3.exceptions import NotFoundError
13
+ from simple_salesforce.exceptions import SalesforceError
14
+
15
+ from cumulusci.core.config import (
16
+ BaseConfig,
17
+ BaseProjectConfig,
18
+ BaseTaskFlowConfig,
19
+ OrgConfig,
20
+ ServiceConfig,
21
+ UniversalConfig,
22
+ )
23
+ from cumulusci.core.config.org_config import VersionInfo
24
+ from cumulusci.core.dependencies.dependencies import (
25
+ PackageNamespaceVersionDependency,
26
+ PackageVersionIdDependency,
27
+ )
28
+ from cumulusci.core.exceptions import (
29
+ ConfigError,
30
+ CumulusCIException,
31
+ DependencyResolutionError,
32
+ FlowNotFoundError,
33
+ KeychainNotFound,
34
+ NamespaceNotFoundError,
35
+ ServiceNotConfigured,
36
+ TaskNotFoundError,
37
+ VcsException,
38
+ )
39
+ from cumulusci.core.keychain.base_project_keychain import (
40
+ DEFAULT_CONNECTED_APP,
41
+ BaseProjectKeychain,
42
+ )
43
+ from cumulusci.core.source import LocalFolderSource
44
+ from cumulusci.tests.util import CURRENT_SF_API_VERSION, DummyKeychain
45
+ from cumulusci.utils import temporary_dir, touch
46
+ from cumulusci.utils.version_strings import StrictVersion
47
+ from cumulusci.utils.yaml.cumulusci_yml import GitHubSourceModel, LocalFolderSourceModel
48
+
49
+
50
+ class FakeConfig(BaseConfig):
51
+ foo: dict
52
+
53
+
54
+ class TestBaseConfig:
55
+ def test_getattr_toplevel_key(self):
56
+ config = FakeConfig()
57
+ config.config = {"foo": "bar"}
58
+ assert config.foo == "bar"
59
+
60
+ def test_getattr_toplevel_key_missing(self):
61
+ config = BaseConfig()
62
+ config.config = {}
63
+ with mock.patch(
64
+ "cumulusci.core.config.base_config.STRICT_GETATTR", False
65
+ ), pytest.warns(DeprecationWarning, match="foo"):
66
+ assert config.foo is None
67
+ with mock.patch(
68
+ "cumulusci.core.config.base_config.STRICT_GETATTR", True
69
+ ), pytest.deprecated_call(), pytest.raises(AssertionError):
70
+ assert config.foo is None
71
+
72
+ def test_getattr_child_key(self):
73
+ config = FakeConfig()
74
+ config.config = {"foo": {"bar": "baz"}}
75
+ assert config.foo__bar == "baz"
76
+
77
+ def test_strict_getattr(self):
78
+ config = FakeConfig()
79
+ config.config = {"foo": {"bar": "baz"}}
80
+ with mock.patch(
81
+ "cumulusci.core.config.base_config.STRICT_GETATTR", "True"
82
+ ), mock.patch("warnings.warn"), pytest.raises(AssertionError):
83
+ print(config.jfiesojfieoj)
84
+
85
+ def test_getattr_child_parent_key_missing(self):
86
+ config = FakeConfig()
87
+ config.config = {}
88
+ assert config.foo__bar is None
89
+
90
+ def test_getattr_child_key_missing(self):
91
+ config = FakeConfig()
92
+ config.config = {"foo": {}}
93
+ assert config.foo__bar is None
94
+
95
+ def test_getattr_default_toplevel(self):
96
+ config = FakeConfig()
97
+ config.config = {"foo": "bar"}
98
+ config.defaults = {"foo": "default"}
99
+ assert config.foo == "bar"
100
+
101
+ def test_getattr_default_toplevel_missing_default(self):
102
+ config = FakeConfig()
103
+ config.config = {"foo": "bar"}
104
+ config.defaults = {}
105
+ assert config.foo == "bar"
106
+
107
+ def test_getattr_default_toplevel_missing_config(self):
108
+ config = FakeConfig()
109
+ config.config = {}
110
+ config.defaults = {"foo": "default"}
111
+ assert config.foo == "default"
112
+
113
+ def test_getattr_default_child(self):
114
+ config = FakeConfig()
115
+ config.config = {"foo": {"bar": "baz"}}
116
+ config.defaults = {"foo__bar": "default"}
117
+ assert config.foo__bar == "baz"
118
+
119
+ def test_getattr_default_child_missing_default(self):
120
+ config = FakeConfig()
121
+ config.config = {"foo": {"bar": "baz"}}
122
+ config.defaults = {}
123
+ assert config.foo__bar == "baz"
124
+
125
+ def test_getattr_default_child_missing_config(self):
126
+ config = FakeConfig()
127
+ config.config = {}
128
+ config.defaults = {"foo__bar": "default"}
129
+ assert config.foo__bar == "default"
130
+
131
+
132
+ class DummyContents(object):
133
+ def __init__(self, content):
134
+ self.decoded = content
135
+
136
+
137
+ class DummyResponse(object):
138
+ def __init__(self, content, status_code):
139
+ self.content = content
140
+ self.status_code = status_code
141
+
142
+
143
+ class DummyRepository(mock.Mock):
144
+ default_branch = "main"
145
+ _api = "http://"
146
+
147
+ def __init__(self, owner, name, contents, releases=None, commits=None, **kwargs):
148
+ """Passing kwargs to workaround python/cpython#83759"""
149
+ super().__init__()
150
+ self.owner = owner
151
+ self.name = name
152
+ self.html_url = f"https://github.com/{owner}/{name}"
153
+ self.clone_url = self.html_url
154
+ self.session = mock.MagicMock()
155
+ self._contents = contents
156
+ self._releases = releases or []
157
+ self._commits = commits or []
158
+ self._tag_message = kwargs.get(
159
+ "tag_message", "Mock tag message for testing purposes"
160
+ )
161
+
162
+ def file_contents(self, path, **kw):
163
+ try:
164
+ return self._contents[path]
165
+ except KeyError:
166
+ raise AssertionError(f"Accessed unexpected file: {path}")
167
+
168
+ def directory_contents(self, path, **kw):
169
+ try:
170
+ return self._contents[path]
171
+ except KeyError:
172
+ raise NotFoundError(
173
+ DummyResponse(f"Accessed unexpected directory: {path}", 404)
174
+ )
175
+
176
+ def _build_url(self, *args, **kw):
177
+ return self._api
178
+
179
+ def releases(self):
180
+ return iter(self._releases)
181
+
182
+ def latest_release(self):
183
+ for release in self._releases:
184
+ if release.tag_name.startswith("release/"):
185
+ return release
186
+ raise NotFoundError(DummyResponse("", 404))
187
+
188
+ def release_from_tag(self, tag_name):
189
+ for release in self._releases:
190
+ if release.tag_name == tag_name:
191
+ return release
192
+ raise NotFoundError(DummyResponse("", 404))
193
+
194
+ def branch(self, name):
195
+ branch = mock.Mock()
196
+ branch.commit.sha = "commit_sha"
197
+ branch.name = name
198
+ return branch
199
+
200
+ def tag(self, sha):
201
+ tag = mock.Mock()
202
+ tag.object.sha = "tag_sha"
203
+ tag.message = self.tag_message or ""
204
+ return tag
205
+
206
+ def ref(self, s):
207
+ ref = mock.Mock()
208
+ ref.object.sha = "ref_sha"
209
+ return ref
210
+
211
+ def commit(self, c):
212
+ if c in self._commits:
213
+ return self._commits[c]
214
+
215
+ raise NotFoundError(DummyResponse("", 404))
216
+
217
+ @property
218
+ def tag_message(self):
219
+ """A mock property to simulate the tag message."""
220
+ return self._tag_message
221
+
222
+ @tag_message.setter
223
+ def tag_message(self, message):
224
+ """A mock setter to change the tag message."""
225
+ self._tag_message = message
226
+
227
+
228
+ class DummyRelease(object):
229
+ def __init__(self, tag_name, name=None):
230
+ self.tag_name = tag_name
231
+ self.name = name
232
+
233
+
234
+ class DummyGithub(object):
235
+ def __init__(self, repositories):
236
+ self.repositories = repositories
237
+
238
+ def repository(self, owner, name):
239
+ try:
240
+ return self.repositories[name]
241
+ except KeyError:
242
+ raise AssertionError(f"Unexpected repository: {name}")
243
+
244
+
245
+ class DummyGithubRepository(object):
246
+ """A wrapper around DummyRepository that follows the AbstractRepo interface"""
247
+
248
+ def __init__(self, dummy_repo, **kwargs):
249
+ self.repo = dummy_repo # This contains the actual DummyRepository
250
+ self.logger = kwargs.get("logger")
251
+ self.project_config = kwargs.get("project_config")
252
+ self.repo_name = dummy_repo.name
253
+ self.repo_owner = dummy_repo.owner
254
+ self.repo_url = dummy_repo.html_url
255
+
256
+ def latest_release(self):
257
+ """Return the latest release from the wrapped repository"""
258
+ return self.repo.latest_release()
259
+
260
+ def releases(self):
261
+ """Return releases from the wrapped repository"""
262
+ return self.repo.releases()
263
+
264
+ def release_from_tag(self, tag_name):
265
+ """Return release for a specific tag from the wrapped repository"""
266
+ return self.repo.release_from_tag(tag_name)
267
+
268
+
269
+ class DummyService(object):
270
+ """A dummy VCS service that returns DummyGithubRepository instances"""
271
+
272
+ def __init__(self, dummy_github, dummy_project_config=None):
273
+ self.dummy_github = dummy_github
274
+ self.logger = None
275
+ self.project_config = dummy_project_config
276
+
277
+ def get_repository(self, options=None):
278
+ """Return a DummyGithubRepository wrapping the DummyRepository"""
279
+ # For the test, we'll use the CumulusCI repository
280
+ dummy_repo = self.dummy_github.repositories["CumulusCI"]
281
+ if dummy_repo is None:
282
+ return None # This will cause the get_repo() method to raise an exception
283
+ return DummyGithubRepository(
284
+ dummy_repo, logger=self.logger, project_config=self.project_config
285
+ )
286
+
287
+
288
+ class TestBaseProjectConfig:
289
+ maxDiff = None
290
+
291
+ def _make_github(self):
292
+ CUMULUSCI_TEST_REPO = DummyRepository(
293
+ "SFDO-Tooling",
294
+ "CumulusCI-Test",
295
+ {
296
+ "cumulusci.yml": DummyContents(
297
+ b"""
298
+ project:
299
+ name: CumulusCI-Test
300
+ package:
301
+ name: CumulusCI-Test
302
+ namespace: ccitest
303
+ git:
304
+ repo_url: https://github.com/SFDO-Tooling/CumulusCI-Test
305
+ dependencies:
306
+ - github: https://github.com/SFDO-Tooling/CumulusCI-Test-Dep
307
+ service: github
308
+ """
309
+ ),
310
+ "unpackaged/pre": {"pre": {}, "skip": {}},
311
+ "src": {"src": ""},
312
+ "unpackaged/post": {"post": {}, "skip": {}},
313
+ },
314
+ )
315
+
316
+ CUMULUSCI_TEST_DEP_REPO = DummyRepository(
317
+ "SFDO-Tooling",
318
+ "CumulusCI-Test-Dep",
319
+ {
320
+ "cumulusci.yml": DummyContents(
321
+ b"""
322
+ project:
323
+ name: CumulusCI-Test-Dep
324
+ package:
325
+ name: CumulusCI-Test-Dep
326
+ namespace: ccitestdep
327
+ git:
328
+ repo_url: https://github.com/SFDO-Tooling/CumulusCI-Test-Dep
329
+ service:
330
+ service_type: github
331
+ service_alias: alias
332
+ """
333
+ ),
334
+ "unpackaged/pre": {},
335
+ "src": {},
336
+ "unpackaged/post": {},
337
+ },
338
+ [DummyRelease("release/2.0", "2.0")],
339
+ )
340
+
341
+ CUMULUSCI_REPO = DummyRepository(
342
+ "SFDO-Tooling",
343
+ "CumulusCI",
344
+ {},
345
+ [
346
+ DummyRelease("release/1.1", "1.1"),
347
+ DummyRelease("beta-wrongprefix", "wrong"),
348
+ DummyRelease("release/1.0", "1.0"),
349
+ DummyRelease("beta/1.0-Beta_2", "1.0 (Beta 2)"),
350
+ DummyRelease("beta/1.0-Beta_1", "1.0 (Beta 1)"),
351
+ ],
352
+ )
353
+
354
+ return DummyGithub(
355
+ {
356
+ "CumulusCI": CUMULUSCI_REPO,
357
+ "CumulusCI-Test": CUMULUSCI_TEST_REPO,
358
+ "CumulusCI-Test-Dep": CUMULUSCI_TEST_DEP_REPO,
359
+ }
360
+ )
361
+
362
+ def test_config_global(self):
363
+ universal_config = UniversalConfig()
364
+ universal_config.config_global = {}
365
+ config = BaseProjectConfig(universal_config)
366
+ assert universal_config.config_global is config.config_global
367
+
368
+ def test_config_universal(self):
369
+ universal_config = UniversalConfig()
370
+ config = BaseProjectConfig(universal_config)
371
+ assert universal_config.config_universal is config.config_universal
372
+
373
+ def test_repo_info(self):
374
+ env = {
375
+ "CUMULUSCI_AUTO_DETECT": "1",
376
+ "HEROKU_TEST_RUN_ID": "TEST1",
377
+ "HEROKU_TEST_RUN_BRANCH": "main",
378
+ "HEROKU_TEST_RUN_COMMIT_VERSION": "HEAD",
379
+ "CUMULUSCI_REPO_BRANCH": "feature/test",
380
+ "CUMULUSCI_REPO_COMMIT": "HEAD~1",
381
+ "CUMULUSCI_REPO_ROOT": ".",
382
+ "CUMULUSCI_REPO_URL": "https://github.com/SFDO-Tooling/CumulusCI-Test.git",
383
+ }
384
+ with mock.patch.dict(os.environ, env):
385
+ config = BaseProjectConfig(UniversalConfig())
386
+ result = config.repo_info
387
+ assert {
388
+ "ci": "heroku",
389
+ "name": "CumulusCI-Test",
390
+ "owner": "SFDO-Tooling",
391
+ "domain": "github.com",
392
+ "branch": "feature/test",
393
+ "commit": "HEAD~1",
394
+ "root": ".",
395
+ "url": "https://github.com/SFDO-Tooling/CumulusCI-Test.git",
396
+ } == result
397
+
398
+ def test_repo_info_missing_env(self):
399
+ env = {
400
+ "CUMULUSCI_AUTO_DETECT": "1",
401
+ "HEROKU_TEST_RUN_ID": "TEST1",
402
+ "HEROKU_TEST_RUN_BRANCH": "main",
403
+ "HEROKU_TEST_RUN_COMMIT_VERSION": "HEAD",
404
+ "CUMULUSCI_REPO_BRANCH": "feature/test",
405
+ "CUMULUSCI_REPO_COMMIT": "HEAD~1",
406
+ "CUMULUSCI_REPO_ROOT": ".",
407
+ }
408
+ with mock.patch.dict(os.environ, env):
409
+ with pytest.raises(ConfigError):
410
+ config = BaseProjectConfig(UniversalConfig())
411
+ config.repo_info
412
+
413
+ def test_repo_root_from_env(self):
414
+ config = BaseProjectConfig(UniversalConfig())
415
+ config._repo_info = {"root": "."}
416
+ assert config.repo_root == "."
417
+
418
+ def test_server_domain_from_repo_info(self):
419
+ config = BaseProjectConfig(UniversalConfig())
420
+ assert config.server_domain == "github.com"
421
+
422
+ def test_server_domain_no_repo_root(self):
423
+ config = BaseProjectConfig(UniversalConfig())
424
+ with temporary_dir():
425
+ assert config.server_domain is None
426
+
427
+ def test_repo_name_from_repo_info(self):
428
+ config = BaseProjectConfig(UniversalConfig())
429
+ config._repo_info = {"name": "CumulusCI"}
430
+ assert config.repo_name == "CumulusCI"
431
+
432
+ def test_repo_name_no_repo_root(self):
433
+ config = BaseProjectConfig(UniversalConfig())
434
+ with temporary_dir():
435
+ assert config.repo_name is None
436
+
437
+ def test_repo_name_from_git(self):
438
+ config = BaseProjectConfig(UniversalConfig())
439
+ assert config.repo_name == "CumulusCI"
440
+
441
+ def test_repo_url_from_repo_info(self):
442
+ config = BaseProjectConfig(UniversalConfig())
443
+ config._repo_info = {"url": "https://github.com/SFDO-Tooling/CumulusCI"}
444
+ assert config.repo_url == "https://github.com/SFDO-Tooling/CumulusCI"
445
+
446
+ def test_lookup_repo_branch(self):
447
+ config = BaseProjectConfig(UniversalConfig())
448
+ config._repo_info = {"branch": "foo-bar-baz"}
449
+ assert config.lookup("repo_branch") == "foo-bar-baz"
450
+
451
+ def test_repo_url_no_repo_root(self):
452
+ config = BaseProjectConfig(UniversalConfig())
453
+ with temporary_dir():
454
+ assert config.repo_url is None
455
+
456
+ @mock.patch("cumulusci.core.config.project_config.git_path")
457
+ def test_repo_url_from_git(self, git_path):
458
+ git_config_file = "git_config"
459
+ git_path.return_value = git_config_file
460
+ repo_url = "https://github.com/foo/bar.git"
461
+ with open(git_config_file, "w") as f:
462
+ f.writelines(['[remote "origin"]\n' f"\turl = {repo_url}"])
463
+
464
+ config = BaseProjectConfig(UniversalConfig())
465
+ assert repo_url == config.repo_url
466
+
467
+ os.remove(git_config_file)
468
+
469
+ def test_repo_owner_from_repo_info(self):
470
+ config = BaseProjectConfig(UniversalConfig())
471
+ config._repo_info = {"owner": "SFDO-Tooling"}
472
+ assert config.repo_owner == "SFDO-Tooling"
473
+
474
+ def test_repo_owner_no_repo_root(self):
475
+ config = BaseProjectConfig(UniversalConfig())
476
+ with temporary_dir():
477
+ assert config.repo_owner is None
478
+
479
+ def test_repo_branch_from_repo_info(self):
480
+ config = BaseProjectConfig(UniversalConfig())
481
+ config._repo_info = {"branch": "main"}
482
+ assert config.repo_branch == "main"
483
+
484
+ def test_repo_branch_no_repo_root(self):
485
+ config = BaseProjectConfig(UniversalConfig())
486
+ with temporary_dir():
487
+ assert config.repo_branch is None
488
+
489
+ def test_repo_commit_from_repo_info(self):
490
+ config = BaseProjectConfig(UniversalConfig())
491
+ config._repo_info = {"commit": "abcdef"}
492
+ assert config.repo_commit == "abcdef"
493
+
494
+ def test_repo_commit_no_repo_root(self):
495
+ config = BaseProjectConfig(UniversalConfig())
496
+ with temporary_dir():
497
+ assert config.repo_commit is None
498
+
499
+ def test_repo_commit_no_repo_branch(self):
500
+ config = BaseProjectConfig(UniversalConfig())
501
+ with temporary_dir() as d:
502
+ os.mkdir(os.path.join(d, ".git"))
503
+ with open(os.path.join(d, ".git", "HEAD"), "w") as f:
504
+ f.write("abcdef")
505
+
506
+ assert config.repo_commit == "abcdef"
507
+
508
+ def test_repo_commit_packed_refs(self):
509
+ config = BaseProjectConfig(UniversalConfig())
510
+ with temporary_dir() as d:
511
+ os.system("git init")
512
+ with open(os.path.join(d, ".git", "HEAD"), "w") as f:
513
+ f.write("ref: refs/heads/main\n")
514
+ with open(os.path.join(d, ".git", "packed-refs"), "w") as f:
515
+ f.write("# pack-refs with: peeled fully-peeled sorted\n")
516
+ f.write("#\n")
517
+ f.write(
518
+ "8ce67f4519190cd1ec9785105168e21b9599bc27 refs/remotes/origin/main\n"
519
+ )
520
+
521
+ assert config.repo_commit is not None
522
+
523
+ @mock.patch("cumulusci.vcs.bootstrap.get_repo_from_url")
524
+ def test_get_repo_from_url(self, mock_get_repo):
525
+ config = BaseProjectConfig(
526
+ UniversalConfig(),
527
+ {},
528
+ )
529
+
530
+ mock_repo = mock.Mock()
531
+ mock_get_repo.return_value = mock_repo
532
+
533
+ result = config.get_repo_from_url("https://github.com/Test/TestRepo")
534
+
535
+ # Assert the result is the same repo returned by the bootstrap function
536
+ assert result == mock_repo
537
+
538
+ # Assert the bootstrap function was called with the correct arguments
539
+ mock_get_repo.assert_called_once_with(
540
+ config, "https://github.com/Test/TestRepo"
541
+ )
542
+
543
+ # Assert that the logger was set on the returned repo
544
+ assert mock_repo.logger == config.logger
545
+
546
+ def test_get_latest_tag(self):
547
+ config = BaseProjectConfig(
548
+ UniversalConfig(),
549
+ {
550
+ "project": {
551
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
552
+ }
553
+ },
554
+ )
555
+ # Mock the repo service to return our DummyService
556
+ dummy_github = self._make_github()
557
+ dummy_service = DummyService(dummy_github, config)
558
+ dummy_service.logger = config.logger
559
+ config._repo_info = {"vcs_service": dummy_service}
560
+
561
+ result = config.get_latest_tag()
562
+ assert result == "release/1.1"
563
+
564
+ def test_get_package_data(self):
565
+ config = BaseProjectConfig(
566
+ UniversalConfig(),
567
+ {
568
+ "project": {
569
+ "package": {"namespace": "foo"},
570
+ }
571
+ },
572
+ )
573
+
574
+ assert BaseProjectConfig.get_package_data(config) == (
575
+ "Package",
576
+ "foo",
577
+ )
578
+
579
+ def test_get_latest_tag_matching_prefix(self):
580
+ config = BaseProjectConfig(
581
+ UniversalConfig(),
582
+ {"project": {"git": {"prefix_beta": "beta/", "prefix_release": "rel/"}}},
583
+ )
584
+ github = self._make_github()
585
+ github.repositories["CumulusCI"]._releases.append(
586
+ DummyRelease("rel/0.9", "0.9")
587
+ )
588
+ dummy_service = DummyService(github, config)
589
+ config._repo_info = {"vcs_service": dummy_service}
590
+
591
+ result = config.get_latest_tag()
592
+ assert result == "rel/0.9"
593
+
594
+ def test_get_latest_tag_beta(self):
595
+ config = BaseProjectConfig(
596
+ UniversalConfig(),
597
+ {
598
+ "project": {
599
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
600
+ }
601
+ },
602
+ )
603
+ dummy_github = self._make_github()
604
+ dummy_service = DummyService(dummy_github, config)
605
+ dummy_service.logger = config.logger
606
+ config._repo_info = {"vcs_service": dummy_service}
607
+
608
+ result = config.get_latest_tag(beta=True)
609
+ assert result == "beta/1.0-Beta_2"
610
+
611
+ def test_get_latest_tag__beta_not_found(self):
612
+ config = BaseProjectConfig(UniversalConfig())
613
+ github = self._make_github()
614
+ github.repositories["CumulusCI"]._releases = []
615
+ dummy_service = DummyService(github, config)
616
+ dummy_service.logger = config.logger
617
+ config._repo_info = {"vcs_service": dummy_service}
618
+
619
+ with pytest.raises(VcsException):
620
+ config.get_latest_tag(beta=True)
621
+
622
+ def test_get_latest_tag__repo_not_found(self):
623
+ config = BaseProjectConfig(UniversalConfig())
624
+ github = self._make_github()
625
+ github.repositories["CumulusCI"] = None
626
+ dummy_service = DummyService(github, config)
627
+ dummy_service.logger = config.logger
628
+ config._repo_info = {"vcs_service": dummy_service}
629
+
630
+ with pytest.raises(VcsException):
631
+ config.get_latest_tag()
632
+
633
+ def test_get_latest_tag__release_not_found(self):
634
+ config = BaseProjectConfig(UniversalConfig())
635
+ github = self._make_github()
636
+ github.repositories["CumulusCI"]._releases = []
637
+ dummy_service = DummyService(github, config)
638
+ dummy_service.logger = config.logger
639
+ config._repo_info = {"vcs_service": dummy_service}
640
+
641
+ with pytest.raises(VcsException):
642
+ config.get_latest_tag()
643
+
644
+ def test_get_latest_version(self):
645
+ config = BaseProjectConfig(
646
+ UniversalConfig(),
647
+ {
648
+ "project": {
649
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
650
+ }
651
+ },
652
+ )
653
+ dummy_github = self._make_github()
654
+ dummy_service = DummyService(dummy_github, config)
655
+ dummy_service.logger = config.logger
656
+ config._repo_info = {"vcs_service": dummy_service}
657
+
658
+ result = config.get_latest_version()
659
+ assert result == "1.1"
660
+
661
+ def test_get_latest_version_beta(self):
662
+ config = BaseProjectConfig(
663
+ UniversalConfig(),
664
+ {
665
+ "project": {
666
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
667
+ }
668
+ },
669
+ )
670
+ dummy_github = self._make_github()
671
+ dummy_service = DummyService(dummy_github, config)
672
+ dummy_service.logger = config.logger
673
+ config._repo_info = {"vcs_service": dummy_service}
674
+
675
+ result = config.get_latest_version(beta=True)
676
+ assert result == "1.0 (Beta 2)"
677
+
678
+ def test_get_previous_version(self):
679
+ config = BaseProjectConfig(
680
+ UniversalConfig(),
681
+ {
682
+ "project": {
683
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
684
+ }
685
+ },
686
+ )
687
+ dummy_github = self._make_github()
688
+ dummy_service = DummyService(dummy_github, config)
689
+ dummy_service.logger = config.logger
690
+ config._repo_info = {"vcs_service": dummy_service}
691
+
692
+ result = config.get_previous_version()
693
+ assert result == "1.0"
694
+
695
+ def test_config_project_path_no_repo_root(self):
696
+ config = BaseProjectConfig(UniversalConfig())
697
+ with temporary_dir():
698
+ assert config.config_project_path is None
699
+
700
+ def test_get_tag_for_version(self):
701
+ config = BaseProjectConfig(
702
+ UniversalConfig(), {"project": {"git": {"prefix_release": "release/"}}}
703
+ )
704
+ assert config.get_tag_for_version("beta/", "1.0") == "beta/1.0"
705
+
706
+ def test_get_tag_for_version__1gp_beta(self):
707
+ config = BaseProjectConfig(
708
+ UniversalConfig(), {"project": {"git": {"prefix_beta": "beta/"}}}
709
+ )
710
+ assert config.get_tag_for_version("beta/", "1.0 (Beta 1)") == "beta/1.0-Beta_1"
711
+
712
+ def test_get_tag_for_version__with_tag_prefix_option(self):
713
+ config = BaseProjectConfig(UniversalConfig(), {})
714
+ assert config.get_tag_for_version("custom/", "1.0") == "custom/1.0"
715
+
716
+ def test_get_version_for_tag(self):
717
+ config = BaseProjectConfig(
718
+ UniversalConfig(),
719
+ {
720
+ "project": {
721
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
722
+ }
723
+ },
724
+ )
725
+ assert config.get_version_for_tag("release/1.0") == "1.0"
726
+
727
+ def test_get_version_for_tag_invalid_beta(self):
728
+ config = BaseProjectConfig(
729
+ UniversalConfig(),
730
+ {
731
+ "project": {
732
+ "git": {"prefix_beta": "beta/", "prefix_release": "release/"}
733
+ }
734
+ },
735
+ )
736
+ assert config.get_version_for_tag("beta/invalid-format") is None
737
+
738
+ def test_check_keychain(self):
739
+ config = BaseProjectConfig(UniversalConfig())
740
+ with pytest.raises(KeychainNotFound):
741
+ config._check_keychain()
742
+
743
+ def test_get_task__included_source(self):
744
+ universal_config = UniversalConfig()
745
+ with temporary_dir() as d:
746
+ touch("cumulusci.yml")
747
+ project_config = BaseProjectConfig(
748
+ universal_config,
749
+ {"sources": {"test": {"path": d}}},
750
+ repo_info={"root": Path(__file__).parent.absolute()},
751
+ )
752
+ task_config = project_config.get_task("test:log")
753
+ assert task_config.project_config is not project_config
754
+ assert isinstance(task_config.project_config.source, LocalFolderSource)
755
+
756
+ def test_get_flow__included_source(self):
757
+ universal_config = UniversalConfig()
758
+ with temporary_dir() as d:
759
+ touch("cumulusci.yml")
760
+ project_config = BaseProjectConfig(
761
+ universal_config,
762
+ {"sources": {"test": {"path": d}}},
763
+ repo_info={"root": Path(__file__).parent.absolute()},
764
+ )
765
+ flow_config = project_config.get_flow("test:dev_org")
766
+ assert flow_config.project_config is not project_config
767
+ assert isinstance(flow_config.project_config.source, LocalFolderSource)
768
+
769
+ def test_get_namespace__not_found(self):
770
+ universal_config = UniversalConfig()
771
+ project_config = BaseProjectConfig(universal_config)
772
+ with pytest.raises(NamespaceNotFoundError):
773
+ project_config.get_namespace("test")
774
+
775
+ def test_include_source__bad_spec(self):
776
+ universal_config = UniversalConfig()
777
+ project_config = BaseProjectConfig(
778
+ universal_config, {"sources": {"test": {"foo": "some_nonsense"}}}
779
+ )
780
+ with pytest.raises(ValueError, match="Invalid source spec"):
781
+ project_config.include_source(project_config.get_namespace("test"))
782
+
783
+ def test_include_source__cached(self):
784
+ universal_config = UniversalConfig()
785
+ project_config = BaseProjectConfig(
786
+ universal_config,
787
+ repo_info={"root": os.getcwd()},
788
+ )
789
+ with temporary_dir() as d:
790
+ touch("cumulusci.yml")
791
+ other1 = project_config.include_source(LocalFolderSourceModel(path=d))
792
+ other2 = project_config.include_source(LocalFolderSourceModel(path=d))
793
+ assert other1 is other2
794
+
795
+ @mock.patch("cumulusci.core.config.project_config.VCSSource")
796
+ def test_include_source__github(self, source):
797
+ source.create = expected_result = mock.Mock()
798
+ expected_result.fetch.return_value.repo_root = "/whatever"
799
+ source.create.return_value = expected_result
800
+ universal_config = UniversalConfig()
801
+ project_config = BaseProjectConfig(universal_config)
802
+ other_config = project_config.include_source(
803
+ GitHubSourceModel(github="foo/bar")
804
+ )
805
+ assert other_config.source is expected_result
806
+
807
+ def test_relpath(self):
808
+ universal_config = UniversalConfig()
809
+ project_config = BaseProjectConfig(universal_config)
810
+ assert project_config.relpath(os.path.abspath(".")) == "."
811
+
812
+ def test_validate_package_api_version_valid(self):
813
+ """We stringify the float 46.0 as this is what will occur when
814
+ it is formatted into API URLS. This also negates the need to
815
+ test an explicit string (i.e. if this passes we know that '46.0'
816
+ will also pass)."""
817
+ project_config = BaseProjectConfig(UniversalConfig())
818
+ project_config.config["project"]["package"]["api_version"] = str(46.0)
819
+ project_config._validate_package_api_format()
820
+
821
+ def test_validate_package_api_version_invalid(self):
822
+ project_config = BaseProjectConfig(UniversalConfig())
823
+ project_config.config["project"]["package"]["api_version"] = str([1, 2, 3])
824
+ with pytest.raises(ConfigError):
825
+ project_config._validate_package_api_format()
826
+
827
+ project_config.config["project"]["package"]["api_version"] = "9"
828
+ with pytest.raises(ConfigError):
829
+ project_config._validate_package_api_format()
830
+
831
+ project_config.config["project"]["package"]["api_version"] = "9.0"
832
+ with pytest.raises(ConfigError):
833
+ project_config._validate_package_api_format()
834
+
835
+ project_config.config["project"]["package"]["api_version"] = "45"
836
+ with pytest.raises(ConfigError):
837
+ project_config._validate_package_api_format()
838
+
839
+ project_config.config["project"]["package"]["api_version"] = "45."
840
+ with pytest.raises(ConfigError):
841
+ project_config._validate_package_api_format()
842
+
843
+ project_config.config["project"]["package"]["api_version"] = "45.00"
844
+ with pytest.raises(ConfigError):
845
+ project_config._validate_package_api_format()
846
+
847
+ @mock.patch("cumulusci.core.config.project_config.git_path")
848
+ def test_git_config_remote_origin_line(self, git_path):
849
+ git_config_file = "test_git_config_file"
850
+ git_path.return_value = git_config_file
851
+
852
+ with open(git_config_file, "w") as f:
853
+ f.writelines(
854
+ [
855
+ '[branch "feature-1"]\n',
856
+ "\tremote = origin\n",
857
+ "\tmerge = refs/heads/feature-1\n",
858
+ '[remote "origin"]\n',
859
+ "\tfetch = +refs/heads/*:refs/remotes/origin/*\n",
860
+ ]
861
+ )
862
+
863
+ project_config = BaseProjectConfig(UniversalConfig())
864
+ actual_line = project_config.git_config_remote_origin_url()
865
+ assert actual_line is None # no url under [remote "origin"]
866
+
867
+ with open(git_config_file, "a") as f:
868
+ f.write("\turl = some.url.here\n")
869
+
870
+ actual_line = project_config.git_config_remote_origin_url()
871
+ assert actual_line == "some.url.here"
872
+
873
+ os.remove(git_config_file)
874
+ actual_line = project_config.git_config_remote_origin_url()
875
+ assert actual_line is None # no config file present
876
+
877
+ def test_default_package_path(self):
878
+ config = BaseProjectConfig(UniversalConfig())
879
+ assert str(config.default_package_path.relative_to(config.repo_root)) == "src"
880
+
881
+ def test_default_package_path__sfdx(self):
882
+ with temporary_dir() as path:
883
+ pathlib.Path(path, ".git").mkdir()
884
+ with pathlib.Path(path, "cumulusci.yml").open("w") as f:
885
+ yaml.dump({"project": {"source_format": "sfdx"}}, f)
886
+ with pathlib.Path(path, "sfdx-project.json").open("w") as f:
887
+ json.dump(
888
+ {"packageDirectories": [{"path": "force-app", "default": True}]}, f
889
+ )
890
+ config = BaseProjectConfig(UniversalConfig())
891
+ assert (
892
+ str(config.default_package_path.relative_to(config.repo_root))
893
+ == "force-app"
894
+ )
895
+
896
+
897
+ class TestBaseTaskFlowConfig:
898
+ def setup_method(self):
899
+ self.task_flow_config = BaseTaskFlowConfig(
900
+ {
901
+ "tasks": {
902
+ "deploy": {"description": "Deploy Task", "class_path": "my.path"},
903
+ "no_path": {"description": "Only a Description"},
904
+ "manage": {},
905
+ "control": {},
906
+ },
907
+ "flows": {
908
+ "coffee": {"description": "Coffee Flow"},
909
+ "juice": {"description": "Juice Flow"},
910
+ },
911
+ }
912
+ )
913
+
914
+ def test_list_tasks(self):
915
+ tasks = self.task_flow_config.list_tasks()
916
+ assert len(tasks) == 4
917
+ deploy = [task for task in tasks if task["name"] == "deploy"][0]
918
+ assert deploy["description"] == "Deploy Task"
919
+
920
+ def test_get_task(self):
921
+ task = self.task_flow_config.get_task("deploy")
922
+ assert isinstance(task, BaseConfig)
923
+ assert ("description", "Deploy Task") in task.config.items()
924
+
925
+ def test_get_task__no_class_path(self):
926
+ with pytest.raises(
927
+ CumulusCIException, match="Task has no class_path defined: no_path"
928
+ ):
929
+ self.task_flow_config.get_task("no_path")
930
+
931
+ def test_get_task__no_config_found(self):
932
+ with pytest.raises(
933
+ CumulusCIException, match="No configuration found for task: manage"
934
+ ):
935
+ self.task_flow_config.get_task("manage")
936
+
937
+ def test_no_task__no_suggestion(self):
938
+ with pytest.raises(
939
+ TaskNotFoundError, match="Task not found: robotic_superstar"
940
+ ):
941
+ self.task_flow_config.get_task("robotic_superstar")
942
+
943
+ def test_no_task__with_suggestion(self):
944
+ with pytest.raises(
945
+ TaskNotFoundError, match='Task not found: mange. Did you mean "manage"?'
946
+ ):
947
+ self.task_flow_config.get_task("mange")
948
+
949
+ def test_get_flow(self):
950
+ flow = self.task_flow_config.get_flow("coffee")
951
+ assert isinstance(flow, BaseConfig)
952
+ assert ("description", "Coffee Flow") in flow.config.items()
953
+
954
+ def test_no_flow(self):
955
+ with pytest.raises(FlowNotFoundError):
956
+ self.task_flow_config.get_flow("water")
957
+
958
+ def test_list_flows(self):
959
+ flows = self.task_flow_config.list_flows()
960
+ assert len(flows) == 2
961
+ coffee = [flow for flow in flows if flow["name"] == "coffee"][0]
962
+ assert coffee["description"] == "Coffee Flow"
963
+
964
+ def test_suggested_name(self):
965
+ flows = self.task_flow_config.flows
966
+ assert len(flows) == 2
967
+ error_msg = self.task_flow_config.get_suggested_name("bofee", flows)
968
+ assert "coffee" in error_msg
969
+
970
+
971
+ class TestOrgConfig:
972
+ @mock.patch("cumulusci.core.config.org_config.OrgConfig.OAuth2Client")
973
+ def test_refresh_oauth_token(self, OAuth2Client):
974
+ config = OrgConfig(
975
+ {
976
+ "refresh_token": mock.sentinel.refresh_token,
977
+ "instance_url": "http://instance_url_111.com",
978
+ },
979
+ "test",
980
+ )
981
+ project_config = BaseProjectConfig(UniversalConfig())
982
+ keychain = BaseProjectKeychain(project_config, None)
983
+ config._load_userinfo = mock.Mock()
984
+ config._load_orginfo = mock.Mock()
985
+
986
+ refresh_token = mock.Mock(return_value={"access_token": "asdf"})
987
+ OAuth2Client.return_value = mock.Mock(refresh_token=refresh_token)
988
+
989
+ config.refresh_oauth_token(keychain)
990
+
991
+ client_config = OAuth2Client.call_args[0][0]
992
+ assert client_config.client_id == DEFAULT_CONNECTED_APP.client_id
993
+ refresh_token.assert_called_once_with(mock.sentinel.refresh_token)
994
+
995
+ @mock.patch("cumulusci.core.config.org_config.OrgConfig.OAuth2Client")
996
+ def test_refresh_oauth_token__other_connected_app(self, OAuth2Client):
997
+ config = OrgConfig(
998
+ {
999
+ "connected_app": "other",
1000
+ "refresh_token": mock.sentinel.refresh_token,
1001
+ "instance_url": "http://instance_url_111.com",
1002
+ },
1003
+ "test",
1004
+ )
1005
+ project_config = BaseProjectConfig(UniversalConfig())
1006
+ keychain = BaseProjectKeychain(project_config, None)
1007
+ keychain.set_service(
1008
+ "connected_app",
1009
+ "other",
1010
+ ServiceConfig(
1011
+ {
1012
+ "login_url": "https://other",
1013
+ "callback_url": "http://localhost:8080/callback",
1014
+ "client_id": "OTHER_ID",
1015
+ "client_secret": "OTHER_SECRET",
1016
+ }
1017
+ ),
1018
+ )
1019
+ config._load_userinfo = mock.Mock()
1020
+ config._load_orginfo = mock.Mock()
1021
+
1022
+ refresh_token = mock.Mock(return_value={"access_token": "asdf"})
1023
+ OAuth2Client.return_value = mock.Mock(refresh_token=refresh_token)
1024
+
1025
+ config.refresh_oauth_token(keychain)
1026
+
1027
+ client_config = OAuth2Client.call_args[0][0]
1028
+ assert client_config.client_id == "OTHER_ID"
1029
+ refresh_token.assert_called_once_with(mock.sentinel.refresh_token)
1030
+
1031
+ @responses.activate
1032
+ def test_load_user_info__bad_json(self):
1033
+ config = OrgConfig(
1034
+ {
1035
+ "refresh_token": mock.sentinel.refresh_token,
1036
+ "instance_url": "http://instance_url_111.com",
1037
+ },
1038
+ "test",
1039
+ )
1040
+ keychain = mock.Mock()
1041
+ keychain.get_service.return_value = mock.Mock(
1042
+ client_id="asdf", client_secret="asdf"
1043
+ )
1044
+
1045
+ responses.add(
1046
+ responses.POST, "http://instance_url_111.com/services/oauth2/token"
1047
+ )
1048
+ with pytest.raises(CumulusCIException) as e:
1049
+ config.refresh_oauth_token(keychain)
1050
+ assert "Cannot decode" in str(e.value)
1051
+
1052
+ def test_refresh_oauth_token_no_connected_app(self):
1053
+ config = OrgConfig({}, "test")
1054
+ with pytest.raises(AttributeError):
1055
+ config.refresh_oauth_token(None)
1056
+
1057
+ def test_refresh_oauth_token__bad_connected_app(self):
1058
+ org_config = OrgConfig({"connected_app": "bogus"}, "test")
1059
+ project_config = BaseProjectConfig(UniversalConfig())
1060
+ keychain = BaseProjectKeychain(project_config, None)
1061
+ with pytest.raises(ServiceNotConfigured, match="no longer configured"):
1062
+ org_config.refresh_oauth_token(keychain)
1063
+
1064
+ @mock.patch("jwt.encode", mock.Mock(return_value="JWT"))
1065
+ @responses.activate
1066
+ def test_refresh_oauth_token__jwt(self):
1067
+ responses.add(
1068
+ "POST",
1069
+ "https://login.salesforce.com/services/oauth2/token",
1070
+ json={
1071
+ "access_token": "TOKEN",
1072
+ "instance_url": "https://na00.salesforce.com",
1073
+ },
1074
+ )
1075
+ with mock.patch.dict(
1076
+ os.environ,
1077
+ {"SFDX_CLIENT_ID": "some client id", "SFDX_HUB_KEY": "some private key"},
1078
+ ):
1079
+ config = OrgConfig({}, "test")
1080
+ config._load_userinfo = mock.Mock()
1081
+ config._load_orginfo = mock.Mock()
1082
+ config.refresh_oauth_token(None)
1083
+ assert config.access_token == "TOKEN"
1084
+
1085
+ @mock.patch("jwt.encode", mock.Mock(return_value="JWT"))
1086
+ @responses.activate
1087
+ def test_refresh_oauth_token__jwt_sandbox(self):
1088
+ responses.add(
1089
+ "POST",
1090
+ "https://cs00.salesforce.com/services/oauth2/token",
1091
+ json={
1092
+ "access_token": "TOKEN",
1093
+ "instance_url": "https://cs00.salesforce.com",
1094
+ },
1095
+ )
1096
+ with mock.patch.dict(
1097
+ os.environ,
1098
+ {"SFDX_CLIENT_ID": "some client id", "SFDX_HUB_KEY": "some private key"},
1099
+ ):
1100
+ config = OrgConfig(
1101
+ {
1102
+ "instance_url": "https://cs00.salesforce.com",
1103
+ },
1104
+ "test",
1105
+ )
1106
+ config._load_userinfo = mock.Mock()
1107
+ config._load_orginfo = mock.Mock()
1108
+ config.refresh_oauth_token(None)
1109
+ assert config.access_token == "TOKEN"
1110
+
1111
+ @mock.patch("jwt.encode", mock.Mock(return_value="JWT"))
1112
+ @responses.activate
1113
+ def test_refresh_oauth_token__jwt_sandbox_instanceless_url(self):
1114
+ responses.add(
1115
+ "POST",
1116
+ "https://nonobvious--sandbox.my.salesforce.com/services/oauth2/token",
1117
+ json={
1118
+ "access_token": "TOKEN",
1119
+ "instance_url": "https://nonobvious--sandbox.my.salesforce.com",
1120
+ },
1121
+ )
1122
+ with mock.patch.dict(
1123
+ os.environ,
1124
+ {"SFDX_CLIENT_ID": "some client id", "SFDX_HUB_KEY": "some private key"},
1125
+ ):
1126
+ config = OrgConfig(
1127
+ {
1128
+ "instance_url": "https://nonobvious--sandbox.my.salesforce.com",
1129
+ "id": "https://test.salesforce.com/asdf",
1130
+ },
1131
+ "test",
1132
+ )
1133
+ config._load_userinfo = mock.Mock()
1134
+ config._load_orginfo = mock.Mock()
1135
+ config.refresh_oauth_token(None)
1136
+ assert config.access_token == "TOKEN"
1137
+
1138
+ def test_lightning_base_url__instance(self):
1139
+ config = OrgConfig({"instance_url": "https://na01.salesforce.com"}, "test")
1140
+ assert config.lightning_base_url == "https://na01.lightning.force.com"
1141
+
1142
+ def test_lightning_base_url__scratch_org(self):
1143
+ config = OrgConfig(
1144
+ {"instance_url": "https://foo.cs42.my.salesforce.com"}, "test"
1145
+ )
1146
+ assert config.lightning_base_url == "https://foo.lightning.force.com"
1147
+
1148
+ def test_lightning_base_url__mydomain(self):
1149
+ config = OrgConfig({"instance_url": "https://foo.my.salesforce.com"}, "test")
1150
+ assert config.lightning_base_url == "https://foo.lightning.force.com"
1151
+
1152
+ @responses.activate
1153
+ def test_get_salesforce_version(self):
1154
+ responses.add(
1155
+ "GET",
1156
+ "https://na01.salesforce.com/services/data",
1157
+ json=[{"version": CURRENT_SF_API_VERSION}],
1158
+ )
1159
+ config = OrgConfig({"instance_url": "https://na01.salesforce.com"}, "test")
1160
+ config.access_token = "TOKEN"
1161
+ assert config.latest_api_version == CURRENT_SF_API_VERSION
1162
+
1163
+ @responses.activate
1164
+ def test_get_salesforce_version_bad_json(self):
1165
+ responses.add("GET", "https://na01.salesforce.com/services/data", "NOTJSON!")
1166
+ config = OrgConfig({"instance_url": "https://na01.salesforce.com"}, "test")
1167
+ config.access_token = "TOKEN"
1168
+ with pytest.raises(CumulusCIException) as e:
1169
+ assert config.latest_api_version == "42.0"
1170
+ assert "NOTJSON" in str(e.value)
1171
+
1172
+ @responses.activate
1173
+ def test_get_salesforce_version_weird_json(self):
1174
+ responses.add(
1175
+ "GET", "https://na01.salesforce.com/services/data", json=["NOTADICT"]
1176
+ )
1177
+ config = OrgConfig({"instance_url": "https://na01.salesforce.com"}, "test")
1178
+ config.access_token = "TOKEN"
1179
+ with pytest.raises(CumulusCIException) as e:
1180
+ assert config.latest_api_version == "42.0"
1181
+ assert "NOTADICT" in str(e.value)
1182
+
1183
+ def test_start_url(self):
1184
+ config = OrgConfig(
1185
+ {"instance_url": "https://na01.salesforce.com", "access_token": "TOKEN"},
1186
+ "test",
1187
+ )
1188
+ assert (
1189
+ "https://na01.salesforce.com/secur/frontdoor.jsp?sid=TOKEN"
1190
+ == config.start_url
1191
+ )
1192
+
1193
+ def test_user_id(self):
1194
+ config = OrgConfig({"id": "org/user"}, "test")
1195
+ assert config.user_id == "user"
1196
+
1197
+ def test_can_delete(self):
1198
+ config = OrgConfig({}, "test")
1199
+ assert not config.can_delete()
1200
+
1201
+ @responses.activate
1202
+ def test_load_orginfo(self):
1203
+ config = OrgConfig(
1204
+ {
1205
+ "instance_url": "https://example.com",
1206
+ "access_token": "TOKEN",
1207
+ "id": "OODxxxxxxxxxxxx/user",
1208
+ },
1209
+ "test",
1210
+ )
1211
+ responses.add(
1212
+ "GET",
1213
+ "https://example.com/services/data",
1214
+ json=[{"version": CURRENT_SF_API_VERSION}],
1215
+ )
1216
+
1217
+ responses.add(
1218
+ "GET",
1219
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/Organization/OODxxxxxxxxxxxx",
1220
+ json={
1221
+ "OrganizationType": "Enterprise Edition",
1222
+ "IsSandbox": False,
1223
+ "InstanceName": "cs420",
1224
+ "NamespacePrefix": "ns",
1225
+ },
1226
+ )
1227
+
1228
+ config._load_orginfo()
1229
+
1230
+ assert config.org_type == "Enterprise Edition"
1231
+ assert config.is_sandbox is False
1232
+ assert config.organization_sobject is not None
1233
+ assert config.namespace == "ns"
1234
+
1235
+ @responses.activate
1236
+ def test_get_community_info__cached(self):
1237
+ """Verify that get_community_info returns data from the cache"""
1238
+ config = OrgConfig({}, "test")
1239
+ config._community_info_cache = {"Kōkua": {"name": "Kōkua"}}
1240
+ info = config.get_community_info("Kōkua")
1241
+ assert info["name"] == "Kōkua"
1242
+
1243
+ @responses.activate
1244
+ def test_get_community_info__fetch_if_not_in_cache(self):
1245
+ """Verify that the internal cache is automatically refreshed
1246
+
1247
+ The cache should be refreshed automatically if the requested community
1248
+ is not in the cache.
1249
+ """
1250
+ responses.add(
1251
+ "GET",
1252
+ "https://test/services/data",
1253
+ json=[{"version": CURRENT_SF_API_VERSION}],
1254
+ )
1255
+
1256
+ responses.add(
1257
+ "GET",
1258
+ f"https://test/services/data/v{CURRENT_SF_API_VERSION}/connect/communities",
1259
+ json={"communities": [{"name": "Kōkua"}]},
1260
+ )
1261
+
1262
+ config = OrgConfig(
1263
+ {"instance_url": "https://test", "access_token": "TOKEN"}, "test"
1264
+ )
1265
+ config._community_info_cache = {}
1266
+ info = config.get_community_info("Kōkua")
1267
+ assert info["name"] == "Kōkua"
1268
+
1269
+ @mock.patch("cumulusci.core.config.org_config.OrgConfig._fetch_community_info")
1270
+ def test_community_info_force_refresh(self, mock_fetch):
1271
+ """Verify that the force_refresh parameter has an effect"""
1272
+ mock_fetch.return_value = {"Kōkua": {"name": "Kōkua"}}
1273
+ config = OrgConfig({}, "test")
1274
+
1275
+ # With the cache seeded with the target community, first
1276
+ # verify that the cache isn't refreshed automatically
1277
+ config._community_info_cache = {"Kōkua": {"name": "Kōkua"}}
1278
+ config.get_community_info("Kōkua")
1279
+ mock_fetch.assert_not_called()
1280
+
1281
+ # Now, set force_refresh and make sure it is refreshed
1282
+ config.get_community_info("Kōkua", force_refresh=True)
1283
+ mock_fetch.assert_called()
1284
+
1285
+ @mock.patch("cumulusci.core.config.org_config.OrgConfig._fetch_community_info")
1286
+ def test_community_info_exception(self, mock_fetch):
1287
+ """Verify an exception is thrown when the community doesn't exist"""
1288
+ config = OrgConfig({}, "test")
1289
+ expected_exception = "Unable to find community information for 'bogus'"
1290
+ with pytest.raises(Exception, match=expected_exception):
1291
+ config.get_community_info("bogus")
1292
+
1293
+ MOCK_TOOLING_PACKAGE_RESULTS = [
1294
+ {
1295
+ "size": 2,
1296
+ "totalSize": 2,
1297
+ "done": True,
1298
+ "records": [
1299
+ {
1300
+ "SubscriberPackage": {
1301
+ "Id": "03350000000DEz4AAG",
1302
+ "NamespacePrefix": "GW_Volunteers",
1303
+ "Name": "PKG1",
1304
+ },
1305
+ "SubscriberPackageVersionId": "04t1T00000070yqQAA",
1306
+ },
1307
+ {
1308
+ "SubscriberPackage": {
1309
+ "Id": "03350000000DEz5AAG",
1310
+ "NamespacePrefix": "GW_Volunteers",
1311
+ "Name": "PKG2",
1312
+ },
1313
+ "SubscriberPackageVersionId": "04t000000000001AAA",
1314
+ },
1315
+ {
1316
+ "SubscriberPackage": {
1317
+ "Id": "03350000000DEz7AAG",
1318
+ "NamespacePrefix": "TESTY",
1319
+ "Name": "PKG3",
1320
+ },
1321
+ "SubscriberPackageVersionId": "04t000000000002AAA",
1322
+ },
1323
+ {
1324
+ "SubscriberPackage": {
1325
+ "Id": "03350000000DEz4AAG",
1326
+ "NamespacePrefix": "blah",
1327
+ "Name": "PKG4",
1328
+ },
1329
+ "SubscriberPackageVersionId": "04t0000000BOGUSAAA",
1330
+ },
1331
+ {
1332
+ "SubscriberPackage": {
1333
+ "Id": "03350000000DEz8AAG",
1334
+ "NamespacePrefix": "error",
1335
+ "Name": "PKG5",
1336
+ },
1337
+ "SubscriberPackageVersionId": "04t0000000ERRORAAA",
1338
+ },
1339
+ ],
1340
+ },
1341
+ {
1342
+ "size": 1,
1343
+ "totalSize": 1,
1344
+ "done": True,
1345
+ "records": [
1346
+ {
1347
+ "Id": "04t1T00000070yqQAA",
1348
+ "MajorVersion": 3,
1349
+ "MinorVersion": 119,
1350
+ "PatchVersion": 0,
1351
+ "BuildNumber": 5,
1352
+ "IsBeta": False,
1353
+ }
1354
+ ],
1355
+ },
1356
+ {
1357
+ "size": 1,
1358
+ "totalSize": 1,
1359
+ "done": True,
1360
+ "records": [
1361
+ {
1362
+ "Id": "04t000000000001AAA",
1363
+ "MajorVersion": 12,
1364
+ "MinorVersion": 0,
1365
+ "PatchVersion": 1,
1366
+ "BuildNumber": 1,
1367
+ "IsBeta": False,
1368
+ }
1369
+ ],
1370
+ },
1371
+ {
1372
+ "size": 1,
1373
+ "totalSize": 1,
1374
+ "done": True,
1375
+ "records": [
1376
+ {
1377
+ "Id": "04t000000000002AAA",
1378
+ "MajorVersion": 1,
1379
+ "MinorVersion": 10,
1380
+ "PatchVersion": 0,
1381
+ "BuildNumber": 5,
1382
+ "IsBeta": True,
1383
+ }
1384
+ ],
1385
+ },
1386
+ {"size": 0, "totalSize": 0, "done": True, "records": []},
1387
+ SalesforceError(None, None, None, None),
1388
+ ]
1389
+
1390
+ @mock.patch("cumulusci.core.config.org_config.OrgConfig.salesforce_client")
1391
+ def test_installed_packages(self, sf):
1392
+ config = OrgConfig({}, "test")
1393
+ sf.restful.side_effect = self.MOCK_TOOLING_PACKAGE_RESULTS
1394
+
1395
+ expected = {
1396
+ "GW_Volunteers": [
1397
+ VersionInfo("04t1T00000070yqQAA", StrictVersion("3.119")),
1398
+ VersionInfo("04t000000000001AAA", StrictVersion("12.0.1")),
1399
+ ],
1400
+ "PKG1": [
1401
+ VersionInfo("04t1T00000070yqQAA", StrictVersion("3.119")),
1402
+ ],
1403
+ "GW_Volunteers@3.119": [
1404
+ VersionInfo("04t1T00000070yqQAA", StrictVersion("3.119"))
1405
+ ],
1406
+ "GW_Volunteers@12.0.1": [
1407
+ VersionInfo("04t000000000001AAA", StrictVersion("12.0.1"))
1408
+ ],
1409
+ "PKG2": [VersionInfo("04t000000000001AAA", StrictVersion("12.0.1"))],
1410
+ "TESTY": [VersionInfo("04t000000000002AAA", StrictVersion("1.10.0b5"))],
1411
+ "TESTY@1.10b5": [
1412
+ VersionInfo("04t000000000002AAA", StrictVersion("1.10.0b5"))
1413
+ ],
1414
+ "03350000000DEz4AAG": [
1415
+ VersionInfo("04t1T00000070yqQAA", StrictVersion("3.119"))
1416
+ ],
1417
+ "03350000000DEz5AAG": [
1418
+ VersionInfo("04t000000000001AAA", StrictVersion("12.0.1"))
1419
+ ],
1420
+ "03350000000DEz7AAG": [
1421
+ VersionInfo("04t000000000002AAA", StrictVersion("1.10.0b5"))
1422
+ ],
1423
+ "PKG3": [
1424
+ VersionInfo(id="04t000000000002AAA", number=StrictVersion("1.10b5"))
1425
+ ],
1426
+ }
1427
+ # get it twice so we can make sure it is cached
1428
+ assert config.installed_packages == expected
1429
+ assert config.installed_packages == expected
1430
+ sf.restful.assert_called()
1431
+
1432
+ sf.restful.reset_mock()
1433
+ sf.restful.side_effect = self.MOCK_TOOLING_PACKAGE_RESULTS
1434
+ config.reset_installed_packages()
1435
+ assert config.installed_packages == expected
1436
+ sf.restful.assert_called()
1437
+
1438
+ @mock.patch("cumulusci.core.config.org_config.OrgConfig.salesforce_client")
1439
+ def test_has_minimum_package_version(self, sf):
1440
+ config = OrgConfig({}, "test")
1441
+ sf.restful.side_effect = self.MOCK_TOOLING_PACKAGE_RESULTS
1442
+
1443
+ assert config.has_minimum_package_version("TESTY", "1.9")
1444
+ assert config.has_minimum_package_version("TESTY", "1.10b5")
1445
+ assert not config.has_minimum_package_version("TESTY", "1.10b6")
1446
+ assert not config.has_minimum_package_version("TESTY", "1.10")
1447
+ assert not config.has_minimum_package_version("npsp", "1.0")
1448
+
1449
+ assert config.has_minimum_package_version("03350000000DEz4AAG", "3.119")
1450
+
1451
+ with pytest.raises(CumulusCIException):
1452
+ config.has_minimum_package_version("GW_Volunteers", "1.0")
1453
+
1454
+ def test_orginfo_cache_dir_global(self):
1455
+ config = OrgConfig(
1456
+ {
1457
+ "instance_url": "http://zombo.com/welcome",
1458
+ "username": "test-example@example.com",
1459
+ },
1460
+ "test",
1461
+ keychain=DummyKeychain(),
1462
+ global_org=True,
1463
+ )
1464
+ with TemporaryDirectory() as t:
1465
+ with mock.patch(
1466
+ "cumulusci.tests.util.DummyKeychain.global_config_dir", Path(t)
1467
+ ):
1468
+ with config.get_orginfo_cache_dir("foo") as directory:
1469
+ assert directory.exists()
1470
+ assert str(t) in directory, (t, directory)
1471
+ assert (
1472
+ str(directory)
1473
+ .replace("\\", "/")
1474
+ .endswith("orginfo/zombo.com__test-example__example.com/foo")
1475
+ ), str(directory).replace("\\", "/")
1476
+ foo = directory / "Foo.txt"
1477
+ with foo.open("w") as f:
1478
+ f.write("Bar")
1479
+ with foo.open("r") as f:
1480
+ assert f.read() == "Bar"
1481
+
1482
+ def test_orginfo_cache_dir_local(self):
1483
+ config = OrgConfig(
1484
+ {
1485
+ "instance_url": "http://zombo.com/welcome",
1486
+ "username": "test-example@example.com",
1487
+ },
1488
+ "test",
1489
+ keychain=DummyKeychain(),
1490
+ global_org=False,
1491
+ )
1492
+ with TemporaryDirectory() as t:
1493
+ with mock.patch("cumulusci.tests.util.DummyKeychain.cache_dir", Path(t)):
1494
+
1495
+ with config.get_orginfo_cache_dir("bar") as directory:
1496
+ assert str(t) in directory, (t, directory)
1497
+ assert (
1498
+ str(directory)
1499
+ .replace("\\", "/")
1500
+ .endswith("orginfo/zombo.com__test-example__example.com/bar")
1501
+ )
1502
+ assert directory.exists()
1503
+ foo = directory / "Foo.txt"
1504
+ with foo.open("w") as f:
1505
+ f.write("Bar")
1506
+ with foo.open("r") as f:
1507
+ assert f.read() == "Bar"
1508
+
1509
+ @responses.activate
1510
+ def test_is_person_accounts_enabled__not_enabled(self):
1511
+ config = OrgConfig(
1512
+ {
1513
+ "instance_url": "https://example.com",
1514
+ "access_token": "TOKEN",
1515
+ "id": "OODxxxxxxxxxxxx/user",
1516
+ },
1517
+ "test",
1518
+ )
1519
+ assert (
1520
+ config._is_person_accounts_enabled is None
1521
+ ), "_is_person_accounts_enabled should be initialized as None"
1522
+
1523
+ responses.add(
1524
+ "GET",
1525
+ "https://example.com/services/data",
1526
+ json=[{"version": CURRENT_SF_API_VERSION}],
1527
+ )
1528
+
1529
+ responses.add(
1530
+ "GET",
1531
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/Account/describe",
1532
+ json={"fields": [{"name": "Id"}]},
1533
+ )
1534
+
1535
+ # Verify checks describe if _is_person_accounts_enabled is None.
1536
+ actual = config.is_person_accounts_enabled
1537
+
1538
+ assert actual is False, ""
1539
+ assert actual == config._is_person_accounts_enabled
1540
+
1541
+ # Verify subsequent calls return cached value.
1542
+ config._is_person_accounts_enabled = True
1543
+
1544
+ assert config._is_person_accounts_enabled == config.is_person_accounts_enabled
1545
+
1546
+ @responses.activate
1547
+ def test_is_person_accounts_enabled__is_enabled(self):
1548
+ config = OrgConfig(
1549
+ {
1550
+ "instance_url": "https://example.com",
1551
+ "access_token": "TOKEN",
1552
+ "id": "OODxxxxxxxxxxxx/user",
1553
+ },
1554
+ "test",
1555
+ )
1556
+ assert (
1557
+ config._is_person_accounts_enabled is None
1558
+ ), "_is_person_accounts_enabled should be initialized as None"
1559
+
1560
+ responses.add(
1561
+ "GET",
1562
+ "https://example.com/services/data",
1563
+ json=[{"version": CURRENT_SF_API_VERSION}],
1564
+ )
1565
+
1566
+ responses.add(
1567
+ "GET",
1568
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/Account/describe",
1569
+ json={"fields": [{"name": "Id"}, {"name": "IsPersonAccount"}]},
1570
+ )
1571
+
1572
+ # Verify checks describe if _is_person_accounts_enabled is None.
1573
+ actual = config.is_person_accounts_enabled
1574
+
1575
+ assert actual is True, ""
1576
+ assert actual == config._is_person_accounts_enabled
1577
+
1578
+ # Verify subsequent calls return cached value.
1579
+ config._is_person_accounts_enabled = False
1580
+
1581
+ assert config._is_person_accounts_enabled == config.is_person_accounts_enabled
1582
+
1583
+ @responses.activate
1584
+ def test_is_multi_currency_enabled__not_enabled(self):
1585
+ config = OrgConfig(
1586
+ {
1587
+ "instance_url": "https://example.com",
1588
+ "access_token": "TOKEN",
1589
+ "id": "OODxxxxxxxxxxxx/user",
1590
+ },
1591
+ "test",
1592
+ )
1593
+ assert (
1594
+ config._multiple_currencies_is_enabled is False
1595
+ ), "_multiple_currencies_is_enabled should be initialized as False"
1596
+
1597
+ # Login call.
1598
+ responses.add(
1599
+ "GET",
1600
+ "https://example.com/services/data",
1601
+ json=[{"version": CURRENT_SF_API_VERSION}],
1602
+ )
1603
+
1604
+ # CurrencyType describe() call.
1605
+ # Since Multiple Currencies is not enabled, CurrencyType Sobject is not exposed.
1606
+ # Therefore, the describe call will result in a 404.
1607
+ responses.add(
1608
+ "GET",
1609
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/CurrencyType/describe",
1610
+ status=404,
1611
+ json={
1612
+ "errorCode": "NOT_FOUND",
1613
+ "message": "The requested resource does not exist",
1614
+ },
1615
+ )
1616
+
1617
+ # Add a second 404 to demonstrate we always check the describe until we detect Multiple Currencies is enabled. From then on, we cache the fact that Multiple Currencies is enabled knowing Multiple Currencies cannot be disabled.
1618
+ responses.add(
1619
+ "GET",
1620
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/CurrencyType/describe",
1621
+ status=404,
1622
+ json={
1623
+ "errorCode": "NOT_FOUND",
1624
+ "message": "The requested resource does not exist",
1625
+ },
1626
+ )
1627
+
1628
+ # Check 1: is_multiple_currencies_enabled should be False since the CurrencyType describe gives a 404.
1629
+ actual = config.is_multiple_currencies_enabled
1630
+ assert (
1631
+ actual is False
1632
+ ), "config.is_multiple_currencies_enabled should be False since the CurrencyType describe returns a 404."
1633
+ assert (
1634
+ config._multiple_currencies_is_enabled is False
1635
+ ), "config._multiple_currencies_is_enabled should still be False since the CurrencyType describe returns a 404."
1636
+
1637
+ # Check 2: We should still get the CurrencyType describe since we never cached that multiple currencies is enabled.
1638
+ actual = config.is_multiple_currencies_enabled
1639
+ assert (
1640
+ actual is False
1641
+ ), "config.is_multiple_currencies_enabled should be False since the CurrencyType describe returns a 404."
1642
+ assert (
1643
+ config._multiple_currencies_is_enabled is False
1644
+ ), "config._multiple_currencies_is_enabled should still be False since the CurrencyType describe returns a 404."
1645
+
1646
+ # We should have made 3 calls: 1 token call + 2 describe calls
1647
+ assert len(responses.calls) == 1 + 2
1648
+
1649
+ @responses.activate
1650
+ def test_is_multi_currency_enabled__is_enabled(self):
1651
+ config = OrgConfig(
1652
+ {
1653
+ "instance_url": "https://example.com",
1654
+ "access_token": "TOKEN",
1655
+ "id": "OODxxxxxxxxxxxx/user",
1656
+ },
1657
+ "test",
1658
+ )
1659
+
1660
+ assert (
1661
+ config._multiple_currencies_is_enabled is False
1662
+ ), "_multiple_currencies_is_enabled should be initialized as False"
1663
+
1664
+ # Token call.
1665
+ responses.add(
1666
+ "GET",
1667
+ "https://example.com/services/data",
1668
+ json=[{"version": CURRENT_SF_API_VERSION}],
1669
+ )
1670
+
1671
+ # CurrencyType describe() call.
1672
+ # Since Multiple Currencies is enabled, so the describe call returns a 200.
1673
+ responses.add(
1674
+ "GET",
1675
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/CurrencyType/describe",
1676
+ json={
1677
+ # The actual payload doesn't matter; only matters is we get a 200.
1678
+ },
1679
+ )
1680
+
1681
+ # Check 1: is_multiple_currencies_enabled should be True since the CurrencyType describe gives a 200.
1682
+ actual = config.is_multiple_currencies_enabled
1683
+ assert (
1684
+ actual is True
1685
+ ), "config.is_multiple_currencies_enabled should be True since the CurrencyType describe returns a 200."
1686
+ assert (
1687
+ config._multiple_currencies_is_enabled is True
1688
+ ), "config._multiple_currencies_is_enabled should be True since the CurrencyType describe returns a 200."
1689
+
1690
+ # Check 2: We should have cached that Multiple Currencies is enabled, so we should not make a 2nd descrobe call. This is ok to cache since Multiple Currencies cannot be disabled.
1691
+ actual = config.is_multiple_currencies_enabled
1692
+ assert (
1693
+ actual is True
1694
+ ), "config.is_multiple_currencies_enabled should be True since the our cached value in _multiple_currencies_is_enabled is True."
1695
+ assert (
1696
+ config._multiple_currencies_is_enabled is True
1697
+ ), "config._multiple_currencies_is_enabled should still be True."
1698
+
1699
+ # We should have made 2 calls: 1 token call + 1 describe call
1700
+ assert len(responses.calls) == 1 + 1
1701
+
1702
+ @responses.activate
1703
+ def test_is_advanced_currency_management_enabled__multiple_currencies_not_enabled(
1704
+ self,
1705
+ ):
1706
+ config = OrgConfig(
1707
+ {
1708
+ "instance_url": "https://example.com",
1709
+ "access_token": "TOKEN",
1710
+ "id": "OODxxxxxxxxxxxx/user",
1711
+ },
1712
+ "test",
1713
+ )
1714
+
1715
+ # Token call.
1716
+ responses.add(
1717
+ "GET",
1718
+ "https://example.com/services/data",
1719
+ json=[{"version": CURRENT_SF_API_VERSION}],
1720
+ )
1721
+
1722
+ # DatedConversionRate describe() call.
1723
+ # Since Multiple Currencies is not enabled, DatedConversionRate Sobject is not exposed.
1724
+ # Therefore, the describe call will result in a 404.
1725
+ responses.add(
1726
+ "GET",
1727
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/DatedConversionRate/describe",
1728
+ status=404,
1729
+ json={
1730
+ "errorCode": "NOT_FOUND",
1731
+ "message": "The requested resource does not exist",
1732
+ },
1733
+ )
1734
+
1735
+ # is_advanced_currency_management_enabled should be False since:
1736
+ # - DatedConversionRate describe gives a 404 implying the Sobject is not exposed becuase Multiple Currencies is not enabled.
1737
+ actual = config.is_advanced_currency_management_enabled
1738
+ assert (
1739
+ actual is False
1740
+ ), "config.is_advanced_currency_management_enabled should be False since the describe gives a 404."
1741
+
1742
+ # We should have made 2 calls: 1 token call + 1 describe call
1743
+ assert len(responses.calls) == 1 + 1
1744
+
1745
+ @responses.activate
1746
+ def test_is_advanced_currency_management_enabled__multiple_currencies_enabled__acm_not_enabled(
1747
+ self,
1748
+ ):
1749
+ config = OrgConfig(
1750
+ {
1751
+ "instance_url": "https://example.com",
1752
+ "access_token": "TOKEN",
1753
+ "id": "OODxxxxxxxxxxxx/user",
1754
+ },
1755
+ "test",
1756
+ )
1757
+
1758
+ # Token call.
1759
+ responses.add(
1760
+ "GET",
1761
+ "https://example.com/services/data",
1762
+ json=[{"version": CURRENT_SF_API_VERSION}],
1763
+ )
1764
+
1765
+ # DatedConversionRate describe() call.
1766
+ # Since Multiple Currencies is enabled, so the describe call returns a 200.
1767
+ # However, ACM is not enabled so DatedConversionRate is not createable.
1768
+ responses.add(
1769
+ "GET",
1770
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/DatedConversionRate/describe",
1771
+ json={"createable": False},
1772
+ )
1773
+
1774
+ # is_advanced_currency_management_enabled should be False:
1775
+ # - DatedConversionRate describe gives a 200, so the Sobject is exposed (because Multiple Currencies is enabled).
1776
+ # - But DatedConversionRate is not creatable implying ACM is not enabled.
1777
+ actual = config.is_advanced_currency_management_enabled
1778
+ assert (
1779
+ actual is False
1780
+ ), 'config.is_advanced_currency_management_enabled should be False since though the describe gives a 200, the describe is not "createable".'
1781
+
1782
+ # We should have made 2 calls: 1 token call + 1 describe call
1783
+ assert len(responses.calls) == 1 + 1
1784
+
1785
+ @responses.activate
1786
+ def test_is_advanced_currency_management_enabled__multiple_currencies_enabled__acm_enabled(
1787
+ self,
1788
+ ):
1789
+ config = OrgConfig(
1790
+ {
1791
+ "instance_url": "https://example.com",
1792
+ "access_token": "TOKEN",
1793
+ "id": "OODxxxxxxxxxxxx/user",
1794
+ },
1795
+ "test",
1796
+ )
1797
+
1798
+ # Token call.
1799
+ responses.add(
1800
+ "GET",
1801
+ "https://example.com/services/data",
1802
+ json=[{"version": CURRENT_SF_API_VERSION}],
1803
+ )
1804
+
1805
+ # DatedConversionRate describe() call.
1806
+ # Since Multiple Currencies is enabled, so the describe call returns a 200.
1807
+ # However, ACM is not enabled so DatedConversionRate is not createable.
1808
+ responses.add(
1809
+ "GET",
1810
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/DatedConversionRate/describe",
1811
+ json={"createable": True},
1812
+ )
1813
+
1814
+ # is_advanced_currency_management_enabled should be False:
1815
+ # - DatedConversionRate describe gives a 200, so the Sobject is exposed (because Multiple Currencies is enabled).
1816
+ # - But DatedConversionRate is not creatable implying ACM is not enabled.
1817
+ actual = config.is_advanced_currency_management_enabled
1818
+ assert (
1819
+ actual is True
1820
+ ), 'config.is_advanced_currency_management_enabled should be False since both the describe gives a 200 and the describe is "createable".'
1821
+
1822
+ # We should have made 2 calls: 1 token call + 1 describe call
1823
+ assert len(responses.calls) == 1 + 1
1824
+
1825
+ @responses.activate
1826
+ def test_is_survey_advanced_features_enabled(self):
1827
+ config = OrgConfig(
1828
+ {
1829
+ "instance_url": "https://example.com",
1830
+ "access_token": "TOKEN",
1831
+ "id": "OODxxxxxxxxxxxx/user",
1832
+ },
1833
+ "test",
1834
+ )
1835
+
1836
+ # Token call.
1837
+ responses.add(
1838
+ "GET",
1839
+ "https://example.com/services/data",
1840
+ json=[{"version": CURRENT_SF_API_VERSION}],
1841
+ )
1842
+
1843
+ # describe()
1844
+ responses.add(
1845
+ "GET",
1846
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/PermissionSet/describe",
1847
+ json={"fields": [{"name": "PermissionsAllowSurveyAdvancedFeatures"}]},
1848
+ )
1849
+
1850
+ assert config.is_survey_advanced_features_enabled
1851
+
1852
+ @responses.activate
1853
+ def test_is_survey_advanced_features_enabled__not_enabled(self):
1854
+ config = OrgConfig(
1855
+ {
1856
+ "instance_url": "https://example.com",
1857
+ "access_token": "TOKEN",
1858
+ "id": "OODxxxxxxxxxxxx/user",
1859
+ },
1860
+ "test",
1861
+ )
1862
+
1863
+ # Token call.
1864
+ responses.add(
1865
+ "GET",
1866
+ "https://example.com/services/data",
1867
+ json=[{"version": CURRENT_SF_API_VERSION}],
1868
+ )
1869
+
1870
+ # describe()
1871
+ responses.add(
1872
+ "GET",
1873
+ f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/sobjects/PermissionSet/describe",
1874
+ json={"fields": [{"name": "foo"}]},
1875
+ )
1876
+
1877
+ assert not config.is_survey_advanced_features_enabled
1878
+
1879
+ def test_resolve_04t_dependencies(self):
1880
+ config = OrgConfig({}, "test")
1881
+ config._installed_packages = {
1882
+ "dep@1.0": [VersionInfo("04t000000000001AAA", "1.0")]
1883
+ }
1884
+ result = config.resolve_04t_dependencies(
1885
+ [PackageNamespaceVersionDependency(namespace="dep", version="1.0")]
1886
+ )
1887
+ assert result == [PackageVersionIdDependency(version_id="04t000000000001AAA")]
1888
+
1889
+ def test_resolve_04t_dependencies__not_installed(self):
1890
+ config = OrgConfig({}, "test")
1891
+ config._installed_packages = {}
1892
+ with pytest.raises(DependencyResolutionError):
1893
+ config.resolve_04t_dependencies(
1894
+ [PackageNamespaceVersionDependency(namespace="dep", version="1.0")]
1895
+ )