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,1228 @@
1
+ import datetime
2
+ import json
3
+ import os
4
+ import re
5
+ import sys
6
+ import tempfile
7
+ from pathlib import Path
8
+ from unittest import mock
9
+
10
+ import pytest
11
+
12
+ from cumulusci.core import utils
13
+ from cumulusci.core.config import (
14
+ BaseConfig,
15
+ OrgConfig,
16
+ ScratchOrgConfig,
17
+ ServiceConfig,
18
+ UniversalConfig,
19
+ )
20
+ from cumulusci.core.config.marketing_cloud_service_config import (
21
+ MarketingCloudServiceConfig,
22
+ )
23
+ from cumulusci.core.config.sfdx_org_config import SfdxOrgConfig
24
+ from cumulusci.core.exceptions import (
25
+ ConfigError,
26
+ CumulusCIException,
27
+ CumulusCIUsageError,
28
+ KeychainKeyNotFound,
29
+ OrgCannotBeLoaded,
30
+ OrgNotFound,
31
+ ServiceCannotBeLoaded,
32
+ ServiceNotConfigured,
33
+ ServiceNotValid,
34
+ )
35
+ from cumulusci.core.keychain import EncryptedFileProjectKeychain
36
+ from cumulusci.core.keychain.base_project_keychain import (
37
+ DEFAULT_CONNECTED_APP,
38
+ DEFAULT_CONNECTED_APP_NAME,
39
+ )
40
+ from cumulusci.core.keychain.encrypted_file_project_keychain import (
41
+ SERVICE_ORG_FILE_MODE,
42
+ GlobalOrg,
43
+ )
44
+ from cumulusci.core.keychain.serialization import (
45
+ _simplify_config,
46
+ load_config_from_json_or_pickle,
47
+ )
48
+ from cumulusci.core.tests.utils import EnvironmentVarGuard
49
+ from cumulusci.utils import temporary_dir
50
+
51
+
52
+ @pytest.fixture(params=[True, False])
53
+ def withdifferentformats(request):
54
+ SHOULD_SAVE_AS_JSON = request.param
55
+ with mock.patch(
56
+ "cumulusci.core.keychain.serialization.SHOULD_SAVE_AS_JSON", request.param
57
+ ):
58
+ yield "json" if SHOULD_SAVE_AS_JSON else "pickle"
59
+
60
+
61
+ @pytest.fixture()
62
+ def keychain(project_config, key) -> EncryptedFileProjectKeychain:
63
+ keychain = EncryptedFileProjectKeychain(project_config, key)
64
+ assert keychain.project_config == project_config
65
+ assert keychain.key == key
66
+ return keychain
67
+
68
+
69
+ class TestEncryptedFileProjectKeychain:
70
+
71
+ project_name = "TestProject"
72
+
73
+ def _write_file(self, filepath, contents):
74
+ with open(filepath, "w") as f:
75
+ f.write(contents)
76
+
77
+ def _default_org_path(self, keychain: EncryptedFileProjectKeychain):
78
+ return Path(keychain.global_config_dir, "TestProject/DEFAULT_ORG.txt")
79
+
80
+ def test_cache_dir(self, keychain):
81
+ assert keychain.cache_dir.name == ".cci"
82
+
83
+ def test_raise_service_not_configured(self, keychain):
84
+ with pytest.raises(ServiceNotConfigured):
85
+ keychain._raise_service_not_configured("test-name")
86
+
87
+ #######################################
88
+ # Orgs #
89
+ #######################################
90
+
91
+ def test_set_and_get_org__global(
92
+ self, keychain, org_config, key, withdifferentformats
93
+ ):
94
+ org_config.global_org = True
95
+ keychain.set_org(org_config, True)
96
+ assert list(keychain.orgs.keys()) == ["test"]
97
+ assert _simplify_config(keychain.get_org("test").config) == org_config.config
98
+
99
+ def test_get_org__with_config_properly_overridden(
100
+ self, keychain, scratch_org_config
101
+ ):
102
+ days = 16
103
+ config_file = "./foo/bar/baz"
104
+ scratch_org_config.global_org = True
105
+ # the orgs encrypted file has the default value for days and config_file
106
+ keychain.set_org(scratch_org_config, True)
107
+ # but this particular scratch org has days and config_file specified via cumulusci.yml
108
+ keychain.project_config.config = {
109
+ "orgs": {"scratch": {"test": {"days": days, "config_file": config_file}}}
110
+ }
111
+ org = keychain.get_org("test")
112
+
113
+ # ensure what is configured in cumulusci.yml is what is loaded into the config
114
+ assert org.config["days"] == days
115
+ assert org.config["config_file"] == config_file
116
+
117
+ def test_get_org__not_found(self, keychain):
118
+ org_name = "mythical"
119
+ error_message = f"Org with name '{org_name}' does not exist."
120
+ with pytest.raises(OrgNotFound, match=error_message):
121
+ keychain.get_org(org_name)
122
+
123
+ def test_set_org__no_key_should_save_to_unencrypted_file(
124
+ self, keychain, org_config
125
+ ):
126
+ keychain.key = None
127
+ keychain.set_org(org_config)
128
+
129
+ filepath = Path(keychain.project_local_dir, "test.org")
130
+ with open(filepath, "rb") as f:
131
+ assert load_config_from_json_or_pickle(f.read()) == {
132
+ **org_config.config,
133
+ # still the default for now
134
+ "serialization_format": "pickle",
135
+ }
136
+
137
+ def test_set_org__should_not_save_when_environment_project_keychain_set(
138
+ self, keychain, org_config, withdifferentformats
139
+ ):
140
+ with temporary_dir() as temp:
141
+ env = EnvironmentVarGuard()
142
+ with EnvironmentVarGuard() as env:
143
+ env.set("CUMULUSCI_KEYCHAIN_CLASS", "EnvironmentProjectKeychain")
144
+ with mock.patch.object(
145
+ EncryptedFileProjectKeychain, "project_local_dir", temp
146
+ ):
147
+ keychain.set_org(org_config, global_org=False)
148
+
149
+ actual_org = keychain.get_org("test")
150
+ assert actual_org.config == {
151
+ **org_config.config,
152
+ "serialization_format": withdifferentformats,
153
+ }
154
+ assert not Path(temp, "test.org").is_file()
155
+
156
+ @mock.patch("cumulusci.core.keychain.encrypted_file_project_keychain.open")
157
+ def test_save_org_when_no_project_local_dir_present(
158
+ self, mock_open, keychain, org_config
159
+ ):
160
+ with mock.patch.object(EncryptedFileProjectKeychain, "project_local_dir", None):
161
+ keychain._save_org("alias", org_config, global_org=False)
162
+ assert mock_open.call_count == 0
163
+
164
+ def test_load_files__org_empty(self, keychain):
165
+ self._write_file(
166
+ Path(keychain.global_config_dir, "test.org"),
167
+ keychain._get_config_bytes(BaseConfig({"foo": "bar"})).decode("utf-8"),
168
+ )
169
+
170
+ del keychain.config["orgs"]
171
+ keychain._load_orgs()
172
+ assert "foo" in keychain.get_org("test").config
173
+ assert keychain.get_org("test").keychain == keychain
174
+
175
+ def test_remove_org(self, keychain, org_config):
176
+ keychain.set_org(org_config)
177
+ keychain.remove_org("test")
178
+ assert "test" not in keychain.orgs
179
+
180
+ def test_remove_org__not_found(self, keychain):
181
+ keychain.orgs["test"] = mock.Mock()
182
+ with pytest.raises(OrgNotFound):
183
+ keychain.remove_org("test")
184
+
185
+ def test_remove_org__global_not_found(self, keychain):
186
+ keychain.orgs["test"] = mock.Mock()
187
+ with pytest.raises(OrgNotFound):
188
+ keychain.remove_org("test", True)
189
+
190
+ def test_set_and_get_org_local_should_not_shadow_global(
191
+ self, keychain, org_config, project_config, key, withdifferentformats
192
+ ):
193
+ global_org = True
194
+ org_config.global_org = global_org
195
+ keychain.set_org(org_config, global_org)
196
+ assert ["test"] == list(keychain.orgs.keys())
197
+ assert isinstance(keychain.orgs["test"], GlobalOrg), keychain.orgs["test"]
198
+ test_config = keychain.get_org("test").config
199
+ assert {
200
+ **org_config.config,
201
+ "serialization_format": withdifferentformats,
202
+ } == test_config
203
+
204
+ org_filepath = Path(keychain.global_config_dir, "test.org")
205
+ assert org_filepath.exists()
206
+
207
+ # os.stat returns something different on windows
208
+ # so only check this on osx/linux
209
+ if not sys.platform.startswith("win"):
210
+ # ensure expected file permissions
211
+ stat_result = os.stat(org_filepath)
212
+ actual_mode = oct(stat_result.st_mode & 0o777)
213
+ assert actual_mode == oct(SERVICE_ORG_FILE_MODE)
214
+
215
+ # check that it saves to the right place
216
+ with mock.patch(
217
+ "cumulusci.core.keychain.encrypted_file_project_keychain.os.open"
218
+ ) as o:
219
+ org_config.save()
220
+ opened_file = o.mock_calls[0][1][0]
221
+ assert opened_file.parent.name == ".cumulusci"
222
+ assert opened_file.name == "test.org"
223
+
224
+ # check that it can be loaded in a fresh keychain
225
+ new_keychain = EncryptedFileProjectKeychain(project_config, key)
226
+ org_config = new_keychain.get_org("test")
227
+ assert org_config.global_org
228
+
229
+ def test_get_default_org__with_files(self, keychain, org_config):
230
+ org_config = OrgConfig(org_config.config.copy(), "test", keychain=keychain)
231
+ org_config.save()
232
+ with open(self._default_org_path(keychain), "w") as f:
233
+ f.write("test")
234
+ try:
235
+ assert (
236
+ _simplify_config(keychain.get_default_org()[1].config)
237
+ == org_config.config
238
+ )
239
+ finally:
240
+ self._default_org_path(keychain).unlink()
241
+
242
+ def test_get_default_org__with_files__missing_org(self, keychain):
243
+ with open(self._default_org_path(keychain), "w") as f:
244
+ f.write("should_not_exist")
245
+ assert self._default_org_path(keychain).exists()
246
+ assert keychain.get_default_org() == (None, None)
247
+ assert not self._default_org_path(keychain).exists()
248
+
249
+ @mock.patch("sarge.Command")
250
+ def test_set_default_org__with_files(self, Command, keychain, org_config):
251
+ org_config = OrgConfig(org_config.config.copy(), "test")
252
+ keychain.set_org(org_config)
253
+ keychain.set_default_org("test")
254
+ with open(self._default_org_path(keychain)) as f:
255
+ assert f.read() == "test"
256
+ self._default_org_path(keychain).unlink()
257
+
258
+ @mock.patch("sarge.Command")
259
+ def test_unset_default_org__with_files(self, Command, keychain, org_config):
260
+ org_config = org_config.config.copy()
261
+ org_config = OrgConfig(org_config, "test")
262
+ keychain.set_org(org_config)
263
+ keychain.set_default_org("test")
264
+ keychain.unset_default_org()
265
+ assert keychain.get_default_org()[1] is None
266
+ assert not self._default_org_path(keychain).exists()
267
+
268
+ # old way of finding defaults used contents of the files themselves
269
+ # we should preserve backwards compatibiliity for a few months
270
+ def test_get_default_org__file_missing_fallback(self, keychain, org_config):
271
+ org_config = OrgConfig(org_config.config.copy(), "test", keychain=keychain)
272
+ org_config.config["default"] = True
273
+ org_config.save()
274
+ assert (
275
+ _simplify_config(keychain.get_default_org()[1].config) == org_config.config
276
+ )
277
+
278
+ def test_get_default_org__outside_project(self, keychain):
279
+ assert keychain.get_default_org() == (None, None)
280
+
281
+ def test_load_orgs_from_environment(self, keychain, org_config):
282
+ scratch_config = org_config.config.copy()
283
+ scratch_config["scratch"] = True
284
+ env = EnvironmentVarGuard()
285
+ with EnvironmentVarGuard() as env:
286
+ env.set(
287
+ f"{keychain.env_org_var_prefix}dev",
288
+ json.dumps(scratch_config),
289
+ )
290
+ env.set(
291
+ f"{keychain.env_org_var_prefix}devhub",
292
+ json.dumps(org_config.config),
293
+ )
294
+ keychain._load_orgs_from_environment()
295
+
296
+ actual_config = keychain.get_org("dev")
297
+ assert _simplify_config(actual_config.config) == scratch_config
298
+ actual_config = keychain.get_org("devhub")
299
+ assert _simplify_config(actual_config.config) == org_config.config
300
+
301
+ def test_load_orgs_from_environment__empty_throws_error(self, keychain, org_config):
302
+ env = EnvironmentVarGuard()
303
+ with EnvironmentVarGuard() as env:
304
+ env.set(
305
+ f"{keychain.env_org_var_prefix}dev",
306
+ "",
307
+ )
308
+ with pytest.raises(OrgCannotBeLoaded):
309
+ keychain._load_orgs_from_environment()
310
+
311
+ def test_load_orgs_from_environment__invalid_json_throws_error(
312
+ self, keychain, org_config
313
+ ):
314
+ env = EnvironmentVarGuard()
315
+ with EnvironmentVarGuard() as env:
316
+ env.set(
317
+ f"{keychain.env_org_var_prefix}dev",
318
+ "['foo',]",
319
+ )
320
+ with pytest.raises(OrgCannotBeLoaded):
321
+ keychain._load_orgs_from_environment()
322
+
323
+ #######################################
324
+ # Services #
325
+ #######################################
326
+
327
+ def test_load_service_files(self, keychain):
328
+ github_service_path = Path(f"{keychain.global_config_dir}/services/github")
329
+ self._write_file(
330
+ Path(github_service_path / "alias.service"),
331
+ keychain._get_config_bytes(BaseConfig({"name": "foo"})).decode("utf-8"),
332
+ )
333
+
334
+ # keychain.config["services"] = {}
335
+
336
+ with mock.patch.object(
337
+ EncryptedFileProjectKeychain,
338
+ "global_config_dir",
339
+ keychain.global_config_dir,
340
+ ):
341
+ keychain._load_service_files()
342
+ github_service = keychain.get_service("github", "alias")
343
+ assert "foo" in github_service.config["name"]
344
+
345
+ def test_load_services__from_env(self, keychain):
346
+ service_config_one = ServiceConfig(
347
+ {"name": "foo1", "password": "1234", "token": "1234"}
348
+ )
349
+ service_config_two = ServiceConfig(
350
+ {"name": "foo2", "password": "5678", "token": "5678"}
351
+ )
352
+ with EnvironmentVarGuard() as env:
353
+ env.set(
354
+ f"{keychain.env_service_var_prefix}github",
355
+ json.dumps(service_config_one.config),
356
+ )
357
+ env.set(
358
+ f"{keychain.env_service_var_prefix}github__OTHER",
359
+ json.dumps(service_config_two.config),
360
+ )
361
+ with pytest.raises(ServiceNotConfigured):
362
+ keychain.get_service("github")
363
+
364
+ keychain._load_services_from_environment()
365
+
366
+ gh_service = keychain.get_service("github")
367
+ # this also confirms the default service is set appropriately
368
+ assert _simplify_config(gh_service.config) == service_config_one.config
369
+ gh_service = keychain.get_service("github", "env-OTHER")
370
+ assert _simplify_config(gh_service.config) == service_config_two.config
371
+
372
+ def test_load_services_from_env__same_name_throws_error(self, keychain):
373
+ keychain.logger = mock.Mock()
374
+ service_prefix = EncryptedFileProjectKeychain.env_service_var_prefix
375
+ service_config = ServiceConfig(
376
+ {"name": "foo", "password": "1234", "token": "1234"}
377
+ )
378
+ with EnvironmentVarGuard() as env:
379
+ env.set(f"{service_prefix}github", json.dumps(service_config.config))
380
+ env.set(f"{service_prefix}github", json.dumps(service_config.config))
381
+ keychain._load_services_from_environment()
382
+
383
+ assert 1 == 1
384
+
385
+ def test_load_services_from_env__empty_throws_error(self, keychain):
386
+ service_prefix = EncryptedFileProjectKeychain.env_service_var_prefix
387
+ env = EnvironmentVarGuard()
388
+ with EnvironmentVarGuard() as env:
389
+ env.set(
390
+ f"{service_prefix}github",
391
+ "",
392
+ )
393
+ with pytest.raises(ServiceCannotBeLoaded):
394
+ keychain._load_services_from_environment()
395
+
396
+ def test_load_services_from_env__invalid_json_throws_error(self, keychain):
397
+ service_prefix = EncryptedFileProjectKeychain.env_service_var_prefix
398
+ env = EnvironmentVarGuard()
399
+ with EnvironmentVarGuard() as env:
400
+ env.set(
401
+ f"{service_prefix}github",
402
+ "['foo',]",
403
+ )
404
+ with pytest.raises(ServiceCannotBeLoaded):
405
+ keychain._load_services_from_environment()
406
+
407
+ def test_get_service__built_in_connected_app(self, keychain):
408
+ built_in_connected_app = keychain.get_service("connected_app")
409
+ assert built_in_connected_app is DEFAULT_CONNECTED_APP
410
+
411
+ def test_get_service__with_class_path(self, keychain, service_config):
412
+ encrypted = keychain._get_config_bytes(service_config)
413
+ keychain.config["services"]["marketing_cloud"] = {"foo": encrypted}
414
+ mc_service = keychain._get_service("marketing_cloud", "foo")
415
+
416
+ assert isinstance(mc_service, MarketingCloudServiceConfig)
417
+
418
+ @mock.patch("cumulusci.core.keychain.encrypted_file_project_keychain.import_class")
419
+ def test_get_service__bad_class_path(self, import_class, keychain, service_config):
420
+ import_class.side_effect = AttributeError
421
+ encrypted = keychain._get_config_bytes(service_config)
422
+ keychain.config["services"]["marketing_cloud"] = {"foo": encrypted}
423
+
424
+ error_message = "Unrecognized class_path for service: cumulusci.core.config.marketing_cloud_service_config.MarketingCloudServiceConfig"
425
+ with pytest.raises(CumulusCIException, match=error_message):
426
+ keychain._get_service("marketing_cloud", "foo")
427
+
428
+ def test_get_service__does_not_exist(self, keychain, service_config):
429
+ keychain.set_service("github", "alias", service_config)
430
+ error_message = "No service of type github exists with the name: does-not-exist"
431
+ with pytest.raises(ServiceNotConfigured, match=error_message):
432
+ keychain.get_service("github", "does-not-exist")
433
+
434
+ @pytest.mark.skipif(
435
+ sys.platform.startswith("win"),
436
+ reason="Windows has a different value returned by os.stat",
437
+ )
438
+ def test_set_service_github(self, keychain, service_config):
439
+ keychain.set_service("github", "alias", service_config)
440
+ default_github_service = keychain.get_service("github")
441
+
442
+ service_filepath = Path(
443
+ keychain.global_config_dir, "services/github/alias.service"
444
+ )
445
+ assert service_filepath.is_file()
446
+
447
+ if not sys.platform.startswith("win"):
448
+ # ensure expected file permissions
449
+ stat_result = os.stat(service_filepath)
450
+ actual_mode = oct(stat_result.st_mode & 0o777)
451
+ assert actual_mode == oct(SERVICE_ORG_FILE_MODE)
452
+
453
+ assert default_github_service.config == {
454
+ **service_config.config,
455
+ "token": "test123",
456
+ "serialization_format": "pickle",
457
+ }
458
+
459
+ @pytest.mark.skipif(
460
+ not sys.platform.startswith("win"),
461
+ reason="Windows has a different value returned by os.stat",
462
+ )
463
+ def test_set_service_github__windows_has_correct_file_perms(
464
+ self, keychain, service_config
465
+ ):
466
+ keychain.set_service("github", "alias", service_config)
467
+
468
+ service_filepath = Path(
469
+ keychain.global_config_dir, "services/github/alias.service"
470
+ )
471
+ assert service_filepath.is_file()
472
+
473
+ # ensure expected file permissions
474
+ stat_result = os.stat(service_filepath)
475
+ actual_mode = oct(stat_result.st_mode & 0o777)
476
+ assert actual_mode == "0o666"
477
+
478
+ def test_set_service__cannot_overwrite_default_connected_app(self, keychain):
479
+ connected_app_config = ServiceConfig({"test": "foo"})
480
+ error_message = re.escape(
481
+ f"You cannot use the name {DEFAULT_CONNECTED_APP_NAME} for a connected app service. Please select a different name."
482
+ )
483
+ with pytest.raises(ServiceNotValid, match=error_message):
484
+ keychain.set_service(
485
+ "connected_app", DEFAULT_CONNECTED_APP_NAME, connected_app_config
486
+ )
487
+
488
+ def test_set_service__first_should_be_default(self, keychain):
489
+ keychain.set_service("github", "foo_github", ServiceConfig({"name": "foo"}))
490
+ keychain.set_service("github", "bar_github", ServiceConfig({"name": "bar"}))
491
+
492
+ github_service = keychain.get_service("github")
493
+ assert _simplify_config(github_service.config) == {"name": "foo"}
494
+
495
+ def test_set_default_service(self, keychain, withdifferentformats):
496
+ keychain.set_service("github", "foo_github", ServiceConfig({"name": "foo"}))
497
+ keychain.set_service("github", "bar_github", ServiceConfig({"name": "bar"}))
498
+
499
+ github_service = keychain.get_service("github")
500
+ assert _simplify_config(github_service.config) == {"name": "foo"}
501
+ # now set default to bar
502
+ keychain.set_default_service("github", "bar_github")
503
+ github_service = keychain.get_service("github")
504
+ assert _simplify_config(github_service.config) == {"name": "bar"}
505
+
506
+ def test_set_default_service__service_alredy_default(
507
+ self, keychain, withdifferentformats
508
+ ):
509
+ keychain.set_service("github", "foo_github", ServiceConfig({"name": "foo"}))
510
+ github_service = keychain.get_service("github")
511
+ assert github_service.config == {
512
+ "name": "foo",
513
+ "serialization_format": withdifferentformats,
514
+ }
515
+
516
+ keychain.set_default_service("github", "foo_github")
517
+
518
+ github_service = keychain.get_service("github")
519
+ assert github_service.config == {
520
+ "name": "foo",
521
+ "serialization_format": withdifferentformats,
522
+ }
523
+
524
+ def test_set_default_service__no_such_service(self, keychain):
525
+ with pytest.raises(ServiceNotConfigured):
526
+ keychain.set_default_service("fooey", "alias")
527
+
528
+ def test_set_default_service__no_such_alias(self, keychain):
529
+ keychain.set_service("github", "foo_github", ServiceConfig({"name": "foo"}))
530
+ with pytest.raises(ServiceNotConfigured):
531
+ keychain.set_default_service("github", "wrong_alias")
532
+
533
+ def test_save_default_service__global_default_service(self, keychain):
534
+ with open(Path(keychain.global_config_dir, "DEFAULT_SERVICES.json"), "w") as f:
535
+ f.write(
536
+ json.dumps({"devhub": "current_default", "github": "current_default"})
537
+ )
538
+
539
+ keychain._save_default_service("github", "new_default", project=False)
540
+
541
+ with open(Path(keychain.global_config_dir, "DEFAULT_SERVICES.json"), "r") as f:
542
+ default_services = json.loads(f.read())
543
+
544
+ assert default_services["devhub"] == "current_default"
545
+ assert default_services["github"] == "new_default"
546
+
547
+ with open(Path(keychain.project_local_dir, "DEFAULT_SERVICES.json"), "r") as f:
548
+ project_defaults = json.loads(f.read())
549
+
550
+ assert project_defaults == {}
551
+
552
+ def test_save_default_service__project_default_service(self, keychain):
553
+ with open(Path(keychain.project_local_dir, "DEFAULT_SERVICES.json"), "w") as f:
554
+ f.write(
555
+ json.dumps({"devhub": "current_default", "github": "current_default"})
556
+ )
557
+
558
+ keychain._save_default_service("github", "new_default", project=True)
559
+
560
+ with open(Path(keychain.project_local_dir, "DEFAULT_SERVICES.json"), "r") as f:
561
+ default_services = json.loads(f.read())
562
+
563
+ assert default_services["devhub"] == "current_default"
564
+ assert default_services["github"] == "new_default"
565
+
566
+ with open(Path(keychain.global_config_dir, "DEFAULT_SERVICES.json"), "r") as f:
567
+ global_defaults = json.loads(f.read())
568
+
569
+ assert global_defaults == {}
570
+
571
+ def test_iter_local_project_dirs(self, keychain):
572
+ cci_home_dir = Path(keychain.global_config_dir)
573
+ (cci_home_dir / "logs").mkdir()
574
+ (cci_home_dir / "chewy").mkdir()
575
+ (cci_home_dir / "yoshi").mkdir()
576
+ # services/ and TestProject/ are also presenct in ~/.cumulusci
577
+
578
+ local_project_dirs = list(keychain._iter_local_project_dirs())
579
+ assert cci_home_dir / "TestProject" in local_project_dirs
580
+ assert cci_home_dir / "chewy" in local_project_dirs
581
+ assert cci_home_dir / "yoshi" in local_project_dirs
582
+
583
+ def test_create_default_service_file__file_already_exists(self, key):
584
+ home_dir = tempfile.mkdtemp()
585
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
586
+ cci_home_dir.mkdir(parents=True)
587
+ local_project_dir = cci_home_dir / "test-project"
588
+ local_project_dir.mkdir()
589
+ self._write_file(cci_home_dir / "DEFAULT_SERVICES.json", json.dumps({}))
590
+ with mock.patch.object(
591
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
592
+ ):
593
+ keychain = EncryptedFileProjectKeychain(UniversalConfig(), key)
594
+ keychain._create_default_service_files()
595
+
596
+ assert not (local_project_dir / "DEFAULT_SERVICE.json").is_file()
597
+
598
+ def test_create_default_services_files__without_project_service(self, key):
599
+ home_dir = tempfile.mkdtemp()
600
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
601
+ cci_home_dir.mkdir(parents=True)
602
+
603
+ self._write_file(cci_home_dir / "devhub.service", "<encrypted devhub config>")
604
+ self._write_file(cci_home_dir / "github.service", "<encrypted github config>")
605
+
606
+ # local project dir without a .service file
607
+ (cci_home_dir / "test-project").mkdir()
608
+ # we should ignore everything in the log dir
609
+ log_dir = cci_home_dir / "logs"
610
+ log_dir.mkdir()
611
+ self._write_file(log_dir / "connected_app.service", "<encrypted config>")
612
+
613
+ with mock.patch.object(
614
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
615
+ ):
616
+ # _create_default_services_files() invoked via __init__
617
+ EncryptedFileProjectKeychain(UniversalConfig(), key)
618
+
619
+ default_services_file = cci_home_dir / "DEFAULT_SERVICES.json"
620
+ with open(default_services_file, "r") as f:
621
+ default_services = json.loads(f.read())
622
+
623
+ assert len(default_services.keys()) == 2 # we shouldn't get connected_app
624
+ assert default_services["devhub"] == "global"
625
+ assert default_services["github"] == "global"
626
+
627
+ project_default_services_file = Path(
628
+ f"{cci_home_dir}/test-project/DEFAULT_SERVICES.json"
629
+ )
630
+ with open(project_default_services_file, "r") as f:
631
+ assert json.loads(f.read()) == {}
632
+
633
+ def test_create_default_services_files__with_project_service(self, key):
634
+ home_dir = tempfile.mkdtemp()
635
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
636
+ cci_home_dir.mkdir(parents=True)
637
+
638
+ self._write_file(cci_home_dir / "devhub.service", "<encrypted devhub config>")
639
+ self._write_file(cci_home_dir / "github.service", "<encrypted github config>")
640
+
641
+ project_path = cci_home_dir / "test-project"
642
+ project_path.mkdir(parents=True)
643
+ self._write_file(project_path / "github.service", "project level github config")
644
+
645
+ with mock.patch.object(
646
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
647
+ ):
648
+ with mock.patch.object(
649
+ EncryptedFileProjectKeychain, "project_local_dir", project_path
650
+ ):
651
+ # _create_default_services_files invoked via __init__
652
+ EncryptedFileProjectKeychain(UniversalConfig(), key)
653
+
654
+ global_default_services_file = Path(f"{cci_home_dir}/DEFAULT_SERVICES.json")
655
+ assert global_default_services_file.is_file()
656
+ with open(global_default_services_file, "r") as f:
657
+ global_defaults = json.loads(f.read())
658
+
659
+ assert len(global_defaults.keys()) == 2
660
+ assert global_defaults["github"] == "global"
661
+ assert global_defaults["devhub"] == "global"
662
+
663
+ project_default_services_file = project_path / "DEFAULT_SERVICES.json"
664
+ assert project_default_services_file.is_file()
665
+ with open(project_default_services_file, "r") as f:
666
+ default_services = json.loads(f.read())
667
+
668
+ assert len(default_services.keys()) == 1
669
+ assert default_services["github"] == "test-project"
670
+
671
+ def test_create_services_dir_structure(self, key):
672
+ service_types = list(UniversalConfig().config["services"].keys())
673
+ num_services = len(service_types)
674
+
675
+ # _create_services_dir_structure() is invoked via constructor
676
+ keychain = EncryptedFileProjectKeychain(UniversalConfig(), key)
677
+
678
+ services_path = Path(f"{keychain.global_config_dir}/services")
679
+ for path in Path.iterdir(services_path):
680
+ if path.name in service_types:
681
+ assert Path.is_dir(path)
682
+ service_types.remove(path.name)
683
+
684
+ assert len(service_types) == 0
685
+
686
+ # explicitly invoke a second time to test idempotency
687
+ keychain._create_services_dir_structure()
688
+ # make sure no new dirs appeared
689
+ assert num_services == len(list(Path.iterdir(services_path)))
690
+
691
+ def test_migrate_services(self, key, project_config):
692
+ home_dir = tempfile.mkdtemp()
693
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
694
+ cci_home_dir.mkdir(parents=True)
695
+
696
+ self._write_file(cci_home_dir / "github.service", "github config")
697
+ self._write_file(cci_home_dir / "foo.service", "foo config")
698
+
699
+ local_proj_dir = cci_home_dir / "test-project"
700
+ local_proj_dir.mkdir()
701
+ self._write_file(local_proj_dir / "github.service", "github2 config")
702
+
703
+ with mock.patch.object(
704
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
705
+ ):
706
+ # _migrade_services() invoked via __init__
707
+ EncryptedFileProjectKeychain(project_config, key)
708
+
709
+ assert not Path.is_file(cci_home_dir / "github.service")
710
+
711
+ new_github_service_file = cci_home_dir / "services/github/global.service"
712
+ assert new_github_service_file.is_file()
713
+
714
+ # ensure expected file contents
715
+ with open(cci_home_dir / "services/github/global.service") as f:
716
+ assert f.read() == "github config"
717
+
718
+ assert not Path.is_file(cci_home_dir / "test-project/devhub.service")
719
+ assert (cci_home_dir / "services/github/TestProject.service").is_file()
720
+ with open(cci_home_dir / "services/github/TestProject.service") as f:
721
+ assert f.read() == "github2 config"
722
+
723
+ # unrecognized services should be left alone
724
+ assert (cci_home_dir / "foo.service").is_file()
725
+
726
+ def test_migrate_services__warn_duplicate_default_service(
727
+ self, project_config, key
728
+ ):
729
+ home_dir = tempfile.mkdtemp()
730
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
731
+ cci_home_dir.mkdir(parents=True)
732
+
733
+ # make unnamed devhub service
734
+ legacy_devhub_service = Path(f"{cci_home_dir}/devhub.service")
735
+ self._write_file(legacy_devhub_service, "legacy config")
736
+ # make existing default aliased devhub service
737
+ named_devhub_service = Path(f"{cci_home_dir}/services/devhub/")
738
+ named_devhub_service.mkdir(parents=True)
739
+ self._write_file(f"{named_devhub_service}/global.service", "migrated config")
740
+
741
+ with mock.patch.object(
742
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
743
+ ):
744
+ # _migrate_services() invoked via __init__
745
+ keychain = EncryptedFileProjectKeychain(project_config, key)
746
+ keychain._migrate_services_from_dir(cci_home_dir)
747
+
748
+ # ensure this file wasn't removed
749
+ assert legacy_devhub_service.is_file()
750
+ # ensure contents of migrated are unchanged
751
+ with open(named_devhub_service / "global.service", "r") as f:
752
+ assert f.read() == "migrated config"
753
+
754
+ def test_rename_service(self, keychain, service_config, withdifferentformats):
755
+ home_dir = tempfile.mkdtemp()
756
+
757
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
758
+ cci_home_dir.mkdir()
759
+ with open(cci_home_dir / "DEFAULT_SERVICES.json", "w") as f:
760
+ f.write(json.dumps({"github": "old_alias"}))
761
+
762
+ local_project_dir = cci_home_dir / "test-project"
763
+ local_project_dir.mkdir()
764
+ with open(local_project_dir / "DEFAULT_SERVICES.json", "w") as f:
765
+ f.write(json.dumps({"github": "old_alias"}))
766
+
767
+ github_services_dir = Path(f"{home_dir}/.cumulusci/services/github")
768
+ github_services_dir.mkdir(parents=True)
769
+ self._write_file(github_services_dir / "old_alias.service", "github config")
770
+
771
+ encrypted = keychain._get_config_bytes(service_config)
772
+ keychain.services = {"github": {"old_alias": encrypted}}
773
+ with mock.patch.object(
774
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
775
+ ):
776
+ keychain.rename_service("github", "old_alias", "new_alias")
777
+
778
+ # Getting old alias should fail
779
+ with pytest.raises(ServiceNotConfigured):
780
+ keychain.get_service("github", "old_alias")
781
+
782
+ # Validate new alias has same contents as original
783
+ assert keychain.get_service("github", "new_alias").config == {
784
+ **service_config.config,
785
+ "token": "test123",
786
+ "serialization_format": withdifferentformats,
787
+ }
788
+ # Old service file should be gone
789
+ assert not (github_services_dir / "old_alias.service").is_file()
790
+ # New service file should be present
791
+ assert (github_services_dir / "new_alias.service").is_file()
792
+
793
+ # DEFAULT_SERVICES.json files should have updated aliases
794
+ with open(local_project_dir / "DEFAULT_SERVICES.json", "r") as f:
795
+ assert json.loads(f.read()) == {"github": "new_alias"}
796
+
797
+ with open(cci_home_dir / "DEFAULT_SERVICES.json", "r") as f:
798
+ assert json.loads(f.read()) == {"github": "new_alias"}
799
+
800
+ def test_rename_service__new_alias_already_exists(self, keychain):
801
+ keychain.services = {
802
+ "github": {"old-alias": "old config", "new-alias": "new config"}
803
+ }
804
+ with pytest.raises(
805
+ CumulusCIUsageError,
806
+ match="A service of type github already exists with name: new-alias",
807
+ ):
808
+ keychain.rename_service("github", "old-alias", "new-alias")
809
+
810
+ def test_rename_service__invalid_service_type(self, keychain, service_config):
811
+ keychain.services = {"github": {"alias": "config"}}
812
+ with pytest.raises(ServiceNotConfigured):
813
+ keychain.rename_service("does-not-exist", "old-alias", "new-alias")
814
+
815
+ def test_rename_service__invalid_service_alias(self, keychain, service_config):
816
+ keychain.services = {"github": {"current_alias": "some config"}}
817
+ with pytest.raises(ServiceNotConfigured):
818
+ keychain.rename_service("github", "does-not-exist", "new_alias")
819
+
820
+ def test_rename_alias_in_default_service_file__no_default_present(self, keychain):
821
+ filepath = Path(f"{keychain.global_config_dir}/DEFAULT_SERVICES.json")
822
+ with open(filepath, "w") as f:
823
+ f.write(json.dumps({"saucelabs": "default_alias"}))
824
+ keychain._rename_alias_in_default_service_file(
825
+ filepath, "github", "current_alias", "new_alias"
826
+ )
827
+
828
+ with open(filepath, "r") as f:
829
+ assert json.loads(f.read()) == {"saucelabs": "default_alias"}
830
+
831
+ def test_cannot_rename_cumulusci_default_connected_app(self, keychain):
832
+ error_message = (
833
+ "You cannot rename the connected app service that is provided by CumulusCI."
834
+ )
835
+ with pytest.raises(CumulusCIException, match=error_message):
836
+ keychain.rename_service(
837
+ "connected_app", DEFAULT_CONNECTED_APP_NAME, "new_alias"
838
+ )
839
+
840
+ def test_remove_service(self, keychain, service_config):
841
+ home_dir = tempfile.mkdtemp()
842
+
843
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
844
+ cci_home_dir.mkdir()
845
+ with open(cci_home_dir / "DEFAULT_SERVICES.json", "w") as f:
846
+ f.write(json.dumps({"github": "alias"}))
847
+
848
+ local_project_dir = cci_home_dir / "test-project"
849
+ local_project_dir.mkdir()
850
+ with open(local_project_dir / "DEFAULT_SERVICES.json", "w") as f:
851
+ f.write(json.dumps({"github": "alias"}))
852
+
853
+ github_services_dir = Path(f"{home_dir}/.cumulusci/services/github")
854
+ github_services_dir.mkdir(parents=True)
855
+ self._write_file(github_services_dir / "alias.service", "github config")
856
+
857
+ encrypted = keychain._get_config_bytes(service_config)
858
+ keychain.services = {"github": {"alias": encrypted}}
859
+ keychain._default_services["github"] = "alias"
860
+ with mock.patch.object(
861
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
862
+ ):
863
+ keychain.remove_service("github", "alias")
864
+
865
+ # loaded service is removed
866
+ assert "old-alias" not in keychain.services["github"]
867
+ # corresponding .service file is gone
868
+ assert not (github_services_dir / "alias.service").is_file()
869
+ # references in DEFAULT_SERVICES.json are gone
870
+ with open(cci_home_dir / "DEFAULT_SERVICES.json", "r") as f:
871
+ assert json.loads(f.read()) == {}
872
+ with open(local_project_dir / "DEFAULT_SERVICES.json", "r") as f:
873
+ assert json.loads(f.read()) == {}
874
+ # default service is unset
875
+ assert "github" not in keychain._default_services
876
+
877
+ def test_remove_service__other_set_as_default(self, keychain, service_config):
878
+ home_dir = tempfile.mkdtemp()
879
+
880
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
881
+ cci_home_dir.mkdir()
882
+ with open(cci_home_dir / "DEFAULT_SERVICES.json", "w") as f:
883
+ f.write(json.dumps({"github": "alias"}))
884
+
885
+ local_project_dir = cci_home_dir / "test-project"
886
+ local_project_dir.mkdir()
887
+ with open(local_project_dir / "DEFAULT_SERVICES.json", "w") as f:
888
+ f.write(json.dumps({"github": "alias"}))
889
+
890
+ github_services_dir = Path(f"{home_dir}/.cumulusci/services/github")
891
+ github_services_dir.mkdir(parents=True)
892
+ self._write_file(github_services_dir / "alias.service", "github config")
893
+
894
+ encrypted = keychain._get_config_bytes(service_config)
895
+ keychain.services = {"github": {"alias": encrypted, "other-service": encrypted}}
896
+ keychain._default_services["github"] = "alias"
897
+ with mock.patch.object(
898
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
899
+ ):
900
+ keychain.remove_service("github", "alias")
901
+
902
+ # the one other service should be the default
903
+ assert keychain._default_services["github"] == "other-service"
904
+
905
+ def test_remove_service__cannot_remove_default_connected_app(self, keychain):
906
+ error_message = (
907
+ f"Unable to remove connected app service: {DEFAULT_CONNECTED_APP_NAME}. "
908
+ "This connected app is provided by CumulusCI and cannot be removed."
909
+ )
910
+ with pytest.raises(CumulusCIException, match=error_message):
911
+ keychain.remove_service("connected_app", DEFAULT_CONNECTED_APP_NAME)
912
+
913
+ def test_load_default_connected_app(self, keychain):
914
+ keychain._load_default_connected_app()
915
+ assert (
916
+ DEFAULT_CONNECTED_APP_NAME in keychain.config["services"]["connected_app"]
917
+ )
918
+ assert (
919
+ keychain.config["services"]["connected_app"][DEFAULT_CONNECTED_APP_NAME]
920
+ == DEFAULT_CONNECTED_APP
921
+ )
922
+
923
+ def test_default_connected_app_should_be_default_after_loading(self, keychain):
924
+ keychain._load_default_connected_app()
925
+ assert keychain._default_services["connected_app"] == DEFAULT_CONNECTED_APP_NAME
926
+
927
+ def test_load_default_services(self, keychain):
928
+ expected_defaults = {
929
+ "github": "github_alias",
930
+ "metaci": "metaci_alias",
931
+ "devhub": "devhub_alias",
932
+ "connected_app": "connected_app__alias",
933
+ }
934
+ filepath = Path(f"{keychain.global_config_dir}/DEFAULT_SERVICES.json")
935
+ with open(filepath, "w") as f:
936
+ f.write(json.dumps(expected_defaults))
937
+
938
+ keychain._load_default_services()
939
+ assert keychain._default_services["connected_app"] != DEFAULT_CONNECTED_APP_NAME
940
+ assert keychain._default_services == expected_defaults
941
+
942
+ def test_read_default_services(self, keychain):
943
+ expected_defaults = {
944
+ "github": "github_alias",
945
+ "metaci": "metaci_alias",
946
+ "devhub": "devhub_alias",
947
+ "connected_app": "connected_app__alias",
948
+ }
949
+ filepath = Path(f"{keychain.global_config_dir}/DEFAULT_SERVICES.json")
950
+ with open(filepath, "w") as f:
951
+ f.write(json.dumps(expected_defaults))
952
+
953
+ actual_defaults = keychain._read_default_services(filepath)
954
+ assert actual_defaults == expected_defaults
955
+
956
+ def test_read_default_services__file_does_not_exist(self, keychain):
957
+ assert keychain._read_default_services(Path("not-a-valid-filepath")) == {}
958
+
959
+ def test_write_default_services(self, keychain):
960
+ expected_defaults = {
961
+ "github": "github_alias",
962
+ "apextestdb": "apextestdb_alias",
963
+ "metaci": "metaci_alias",
964
+ "devhub": "devhub_alias",
965
+ "connected_app": "connected_app__alias",
966
+ }
967
+ filepath = Path(f"{keychain.global_config_dir}/DEFAULT_SERVICES.json")
968
+ keychain._write_default_services(filepath, expected_defaults)
969
+
970
+ with open(filepath, "r") as f:
971
+ actual_defaults = json.loads(f.read())
972
+ assert actual_defaults == expected_defaults
973
+
974
+ def test_write_default_services__bad_filename(self, keychain):
975
+ filepath = Path("DEFAULT_THINGS.json")
976
+ with pytest.raises(CumulusCIException):
977
+ keychain._write_default_services(filepath, {})
978
+
979
+ def test_decrypt_config__no_config(self, keychain):
980
+ config = keychain._decrypt_config(OrgConfig, None, extra=["test", keychain])
981
+ assert config.__class__ == OrgConfig
982
+ assert config.config == {}
983
+ assert config.keychain == keychain
984
+
985
+ def test_decrypt_config__no_config_2(self, keychain):
986
+ config = keychain._decrypt_config(BaseConfig, None)
987
+ assert config.__class__ == BaseConfig
988
+ assert config.config == {}
989
+
990
+ def test_decrypt_config__Python_2_warning(self, keychain, caplog):
991
+ config = keychain.cleanup_Python_2_configs({"a": "b"})
992
+ assert config == {"a": "b"}
993
+ assert len(caplog.records) == 0
994
+
995
+ config = keychain.cleanup_Python_2_configs({"a": b"b", b"c": "d"})
996
+ assert config == {"a": "b", "c": "d"}
997
+ assert len(caplog.records) == 2
998
+
999
+ def test_decrypt_config__wrong_key(self, keychain, org_config):
1000
+ keychain.set_org(org_config, False)
1001
+ keychain.key = "x" * 16
1002
+ with pytest.raises(KeychainKeyNotFound):
1003
+ keychain.get_org("test")
1004
+
1005
+ def test_validate_key__wrong_length(self, project_config):
1006
+ with pytest.raises(ConfigError):
1007
+ EncryptedFileProjectKeychain(project_config, "1")
1008
+
1009
+ def test_validate_key__no_key(self, project_config):
1010
+ keychain = EncryptedFileProjectKeychain(project_config, None)
1011
+ assert keychain._validate_key() is None
1012
+
1013
+ def test_construct_config(self, keychain):
1014
+ result = keychain._construct_config(
1015
+ None, [{"scratch": "scratch org"}, "org_name"]
1016
+ )
1017
+ assert isinstance(result, ScratchOrgConfig)
1018
+ result = keychain._construct_config(None, [{"sfdx": True}, "org_name"])
1019
+ assert isinstance(result, SfdxOrgConfig)
1020
+
1021
+ def test_new_service_type_creates_expected_directory(
1022
+ self, keychain, service_config
1023
+ ):
1024
+ home_dir = tempfile.mkdtemp()
1025
+ cci_home_dir = Path(f"{home_dir}/.cumulusci")
1026
+ cci_home_dir.mkdir()
1027
+ services_dir = cci_home_dir / "services"
1028
+ services_dir.mkdir()
1029
+
1030
+ encrypted = keychain._get_config_bytes(service_config)
1031
+ with mock.patch.object(
1032
+ EncryptedFileProjectKeychain, "global_config_dir", cci_home_dir
1033
+ ):
1034
+ keychain._save_encrypted_service("new_service_type", "alias", encrypted)
1035
+
1036
+ assert (services_dir / "new_service_type").is_dir()
1037
+
1038
+ @pytest.mark.parametrize(
1039
+ "val, expected",
1040
+ (
1041
+ ("CUMULUSCI_SERVICE_github", ("github", "env")),
1042
+ ("CUMULUSCI_SERVICE_github__alias", ("github", "env-alias")),
1043
+ ("CUMULUSCI_SERVICE_connected_app", ("connected_app", "env")),
1044
+ ("CUMULUSCI_SERVICE_connected_app__alias", ("connected_app", "env-alias")),
1045
+ ),
1046
+ )
1047
+ def test_get_env_service_type_and_name(self, val, expected, project_config):
1048
+ keychain = EncryptedFileProjectKeychain(project_config, "0123456789abcdef")
1049
+ actual_type, actual_name = keychain._get_env_service_type_and_name(val)
1050
+ assert (actual_type, actual_name) == expected
1051
+
1052
+ def test_backwards_compatability_with_EnvironmentProjectKeychain(
1053
+ self, project_config, key
1054
+ ):
1055
+ """Ensure we don't break backwards compatability for people still using EnvironmentProjectKeychain"""
1056
+ from cumulusci.core.keychain.environment_project_keychain import (
1057
+ EnvironmentProjectKeychain,
1058
+ )
1059
+
1060
+ assert EnvironmentProjectKeychain is EncryptedFileProjectKeychain
1061
+
1062
+
1063
+ def _touch_test_org_file(directory):
1064
+ org_dir = directory / "orginfo/something.something.saleforce.com"
1065
+ org_dir.mkdir(parents=True)
1066
+ (org_dir / "testfile.json").touch()
1067
+ return org_dir
1068
+
1069
+
1070
+ class TestCleanupOrgCacheDir:
1071
+ def test_cleanup_cache_dir(self, keychain):
1072
+ keychain.set_org(
1073
+ OrgConfig({"instance_url": "http://foo.my.salesforce.com/"}, "dev"), False
1074
+ )
1075
+ keychain.set_org(
1076
+ OrgConfig({"instance_url": "http://bar.my.salesforce.com/"}, "qa"), False
1077
+ )
1078
+
1079
+ temp_for_global = tempfile.mkdtemp()
1080
+ with mock.patch.object(
1081
+ EncryptedFileProjectKeychain, "global_config_dir", Path(temp_for_global)
1082
+ ):
1083
+ global_org_dir = _touch_test_org_file(keychain.global_config_dir)
1084
+ temp_for_project = tempfile.mkdtemp()
1085
+ keychain.project_config = mock.Mock()
1086
+
1087
+ cache_dir = keychain.project_config.cache_dir = Path(temp_for_project)
1088
+ project_org_dir = _touch_test_org_file(cache_dir)
1089
+ with mock.patch(
1090
+ "cumulusci.core.keychain.encrypted_file_project_keychain.rmtree"
1091
+ ) as rmtree:
1092
+ keychain.cleanup_org_cache_dirs()
1093
+ rmtree.assert_has_calls(
1094
+ [mock.call(global_org_dir), mock.call(project_org_dir)],
1095
+ any_order=True,
1096
+ )
1097
+
1098
+ def test_cleanup_cache_dir__no_project_config(self, keychain):
1099
+ keychain.project_config = None
1100
+ with mock.patch(
1101
+ "cumulusci.core.keychain.encrypted_file_project_keychain.rmtree"
1102
+ ) as rmtree:
1103
+ keychain.cleanup_org_cache_dirs()
1104
+ assert not rmtree.mock_calls, rmtree.mock_calls
1105
+
1106
+ def test_cleanup_cache_dir_nothing_to_cleanup(self, keychain):
1107
+ keychain.set_org(
1108
+ OrgConfig({"instance_url": "http://foo.my.salesforce.com/"}, "dev"), False
1109
+ )
1110
+
1111
+ keychain.project_config = mock.Mock()
1112
+ temp_for_global = tempfile.mkdtemp()
1113
+ with mock.patch.object(
1114
+ EncryptedFileProjectKeychain, "global_config_dir", Path(temp_for_global)
1115
+ ):
1116
+ temp_for_project = tempfile.mkdtemp()
1117
+ cache_dir = keychain.project_config.cache_dir = Path(temp_for_project)
1118
+ org_dir = cache_dir / "orginfo/foo.my.salesforce.com"
1119
+ org_dir.mkdir(parents=True)
1120
+ (org_dir / "schema.json").touch()
1121
+ with mock.patch(
1122
+ "cumulusci.core.keychain.encrypted_file_project_keychain.rmtree"
1123
+ ) as rmtree:
1124
+ keychain.cleanup_org_cache_dirs()
1125
+ assert not rmtree.mock_calls, rmtree.mock_calls
1126
+
1127
+ duration = (
1128
+ (59, "59s"),
1129
+ (70, "1m:10s"),
1130
+ (119, "1m:59s"),
1131
+ (65, "1m:5s"),
1132
+ (4000, "1h:6m:40s"),
1133
+ (7199, "1h:59m:59s"),
1134
+ )
1135
+
1136
+ @pytest.mark.parametrize("val,expected", duration)
1137
+ def test_time_delta(self, val, expected):
1138
+ formatted = utils.format_duration(datetime.timedelta(seconds=val))
1139
+ assert formatted == expected, (formatted, expected)
1140
+
1141
+ def test_set_and_get_org_with_dates__json(
1142
+ self, keychain, org_config, key, withdifferentformats
1143
+ ):
1144
+ org_config.global_org = True
1145
+ keychain.key = key
1146
+
1147
+ custom_datetime = datetime.datetime.now()
1148
+ custom_date = datetime.datetime.now().date()
1149
+ org_config.config["custom_datetime"] = custom_datetime
1150
+ org_config.config["custom_date"] = custom_date
1151
+
1152
+ keychain.set_org(org_config, True)
1153
+ assert list(keychain.orgs.keys()) == ["test"]
1154
+ config = keychain.get_org("test").config
1155
+ assert config == {
1156
+ **org_config.config,
1157
+ "serialization_format": withdifferentformats,
1158
+ }
1159
+ assert config["custom_datetime"] == custom_datetime
1160
+ assert config["custom_date"] == custom_date
1161
+
1162
+ @mock.patch("cumulusci.core.keychain.serialization.SHOULD_SAVE_AS_JSON", True)
1163
+ def test_set_and_get_org_with_bad_datatypes(self, keychain, org_config, key):
1164
+ org_config.global_org = True
1165
+ keychain.key = None
1166
+ with mock.patch("pickle.dumps") as dumps:
1167
+ dumps.return_value = b"xx"
1168
+ org_config.config["good"] = 25
1169
+
1170
+ keychain.set_org(org_config, True)
1171
+ assert not dumps.mock_calls
1172
+
1173
+ org_config.config["bad"] = 25j
1174
+
1175
+ keychain.set_org(org_config, True)
1176
+ dumps.assert_called_once_with(
1177
+ {"foo": "bar", "good": 25, "bad": 25j}, protocol=mock.ANY
1178
+ )
1179
+
1180
+ def test_set_and_get_service_with_dates__global(
1181
+ self, keychain, key, withdifferentformats
1182
+ ):
1183
+ service_config = ServiceConfig(
1184
+ {"name": "foo1", "password": "1234", "token": "1234"}
1185
+ )
1186
+
1187
+ keychain.key = key
1188
+
1189
+ custom_datetime = datetime.datetime.now()
1190
+ custom_date = datetime.datetime.now().date()
1191
+ service_config.config["custom_datetime"] = custom_datetime
1192
+ service_config.config["custom_date"] = custom_date
1193
+
1194
+ keychain.set_service("github", "alias", service_config)
1195
+ config = keychain.get_service("github").config
1196
+ assert config == {
1197
+ **service_config.config,
1198
+ "serialization_format": withdifferentformats,
1199
+ }
1200
+ assert config["custom_datetime"] == custom_datetime
1201
+ assert config["custom_date"] == custom_date
1202
+
1203
+ def test_migration_pickle_to_json(
1204
+ self, keychain, patch_home_and_env, project_config
1205
+ ):
1206
+ # This all runs in fake-home, not real home.
1207
+ with mock.patch(
1208
+ "cumulusci.core.keychain.serialization.SHOULD_SAVE_AS_JSON", False
1209
+ ):
1210
+ org_config = OrgConfig(
1211
+ {"password": "Kltpzyxm"}, "test_migration", keychain=keychain
1212
+ )
1213
+ org_config.save()
1214
+ assert Path(keychain.project_local_dir, "test_migration.org").exists()
1215
+ del keychain.config["orgs"]
1216
+ keychain._load_orgs()
1217
+ assert keychain.get_org("test_migration").password == "Kltpzyxm"
1218
+ assert keychain.get_org("test_migration").serialization_format == "pickle"
1219
+
1220
+ with mock.patch(
1221
+ "cumulusci.core.keychain.serialization.SHOULD_SAVE_AS_JSON", True
1222
+ ):
1223
+ org_config.save()
1224
+ assert Path(keychain.project_local_dir, "test_migration.org").exists()
1225
+ del keychain.config["orgs"]
1226
+ keychain._load_orgs()
1227
+ assert keychain.get_org("test_migration").password == "Kltpzyxm"
1228
+ assert keychain.get_org("test_migration").serialization_format == "json"