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