regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__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.
Files changed (314) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/api.py +5 -2
  4. regscale/core/app/application.py +36 -6
  5. regscale/core/app/internal/control_editor.py +73 -21
  6. regscale/core/app/internal/evidence.py +727 -204
  7. regscale/core/app/internal/login.py +4 -2
  8. regscale/core/app/internal/model_editor.py +219 -64
  9. regscale/core/app/utils/app_utils.py +86 -12
  10. regscale/core/app/utils/catalog_utils/common.py +1 -1
  11. regscale/core/login.py +21 -4
  12. regscale/core/utils/async_graphql_client.py +363 -0
  13. regscale/core/utils/date.py +77 -1
  14. regscale/dev/cli.py +26 -0
  15. regscale/dev/code_gen.py +109 -24
  16. regscale/dev/version.py +72 -0
  17. regscale/integrations/commercial/__init__.py +30 -2
  18. regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
  19. regscale/integrations/commercial/aws/cli.py +3107 -54
  20. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  21. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  22. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  23. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  24. regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
  25. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  26. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  27. regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
  28. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  29. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  30. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  31. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  32. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  33. regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
  34. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  35. regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
  36. regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
  37. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  38. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  39. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  40. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  41. regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
  42. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  43. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  44. regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
  45. regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
  46. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  47. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  48. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  49. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  50. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  51. regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
  52. regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
  53. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  54. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  55. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  56. regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
  57. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  58. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  59. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  60. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  61. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  62. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  63. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  64. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  65. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  66. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  67. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  68. regscale/integrations/commercial/aws/scanner.py +1072 -205
  69. regscale/integrations/commercial/aws/security_hub.py +319 -0
  70. regscale/integrations/commercial/aws/session_manager.py +282 -0
  71. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  72. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  73. regscale/integrations/commercial/jira.py +489 -153
  74. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  75. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  76. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  77. regscale/integrations/commercial/qualys/__init__.py +167 -68
  78. regscale/integrations/commercial/qualys/scanner.py +305 -39
  79. regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
  80. regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
  81. regscale/integrations/commercial/sicura/api.py +79 -42
  82. regscale/integrations/commercial/sicura/commands.py +8 -2
  83. regscale/integrations/commercial/sicura/scanner.py +83 -44
  84. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  85. regscale/integrations/commercial/synqly/assets.py +133 -16
  86. regscale/integrations/commercial/synqly/edr.py +2 -8
  87. regscale/integrations/commercial/synqly/query_builder.py +536 -0
  88. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  89. regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
  90. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  91. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  92. regscale/integrations/commercial/tenablev2/commands.py +146 -5
  93. regscale/integrations/commercial/tenablev2/scanner.py +1 -3
  94. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  95. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  96. regscale/integrations/commercial/wizv2/click.py +191 -76
  97. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  98. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  99. regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
  100. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  101. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
  102. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
  103. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  104. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  105. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
  106. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  107. regscale/integrations/commercial/wizv2/issue.py +776 -28
  108. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  109. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  110. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  111. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  112. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  113. regscale/integrations/commercial/wizv2/reports.py +243 -0
  114. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  115. regscale/integrations/commercial/wizv2/scanner.py +1031 -441
  116. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  117. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  118. regscale/integrations/commercial/wizv2/variables.py +89 -3
  119. regscale/integrations/compliance_integration.py +1036 -151
  120. regscale/integrations/control_matcher.py +432 -0
  121. regscale/integrations/due_date_handler.py +333 -0
  122. regscale/integrations/milestone_manager.py +291 -0
  123. regscale/integrations/public/__init__.py +14 -0
  124. regscale/integrations/public/cci_importer.py +834 -0
  125. regscale/integrations/public/csam/__init__.py +0 -0
  126. regscale/integrations/public/csam/csam.py +938 -0
  127. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  128. regscale/integrations/public/csam/csam_common.py +154 -0
  129. regscale/integrations/public/csam/csam_controls.py +432 -0
  130. regscale/integrations/public/csam/csam_poam.py +124 -0
  131. regscale/integrations/public/fedramp/click.py +77 -6
  132. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  133. regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
  134. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  135. regscale/integrations/public/fedramp/poam/scanner.py +75 -7
  136. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  137. regscale/integrations/scanner_integration.py +1961 -430
  138. regscale/models/integration_models/CCI_List.xml +1 -0
  139. regscale/models/integration_models/aqua.py +2 -2
  140. regscale/models/integration_models/cisa_kev_data.json +805 -11
  141. regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
  142. regscale/models/integration_models/nexpose.py +36 -10
  143. regscale/models/integration_models/qualys.py +3 -4
  144. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  145. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
  146. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  147. regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
  148. regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
  149. regscale/models/locking.py +12 -8
  150. regscale/models/platform.py +4 -2
  151. regscale/models/regscale_models/__init__.py +7 -0
  152. regscale/models/regscale_models/assessment.py +2 -1
  153. regscale/models/regscale_models/catalog.py +1 -1
  154. regscale/models/regscale_models/compliance_settings.py +251 -1
  155. regscale/models/regscale_models/component.py +1 -0
  156. regscale/models/regscale_models/control_implementation.py +236 -41
  157. regscale/models/regscale_models/control_objective.py +74 -5
  158. regscale/models/regscale_models/file.py +2 -0
  159. regscale/models/regscale_models/form_field_value.py +5 -3
  160. regscale/models/regscale_models/inheritance.py +44 -0
  161. regscale/models/regscale_models/issue.py +301 -102
  162. regscale/models/regscale_models/milestone.py +33 -14
  163. regscale/models/regscale_models/organization.py +3 -0
  164. regscale/models/regscale_models/regscale_model.py +310 -73
  165. regscale/models/regscale_models/security_plan.py +4 -2
  166. regscale/models/regscale_models/vulnerability.py +3 -3
  167. regscale/regscale.py +25 -4
  168. regscale/templates/__init__.py +0 -0
  169. regscale/utils/threading/threadhandler.py +20 -15
  170. regscale/validation/record.py +23 -1
  171. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
  172. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
  173. tests/core/__init__.py +0 -0
  174. tests/core/utils/__init__.py +0 -0
  175. tests/core/utils/test_async_graphql_client.py +472 -0
  176. tests/fixtures/test_fixture.py +13 -8
  177. tests/regscale/core/test_login.py +171 -4
  178. tests/regscale/integrations/commercial/__init__.py +0 -0
  179. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  180. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  181. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  182. tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
  183. tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
  184. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  185. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  186. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  187. tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
  188. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  189. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  190. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  191. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  192. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  193. tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
  194. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  195. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  196. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  197. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  198. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  199. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  200. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  201. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  202. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  203. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  204. tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
  205. tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
  206. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  207. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  208. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  209. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  210. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  211. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  212. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  213. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  214. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  215. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  216. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  217. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  218. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  219. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  220. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  221. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  222. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  223. tests/regscale/integrations/commercial/conftest.py +28 -0
  224. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  225. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  226. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  227. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  228. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  229. tests/regscale/integrations/commercial/test_aws.py +3742 -0
  230. tests/regscale/integrations/commercial/test_burp.py +48 -0
  231. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  232. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  233. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  234. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  235. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  236. tests/regscale/integrations/commercial/test_jira.py +2204 -0
  237. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  238. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  239. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  240. tests/regscale/integrations/commercial/test_sicura.py +349 -0
  241. tests/regscale/integrations/commercial/test_snow.py +423 -0
  242. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  243. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  244. tests/regscale/integrations/commercial/test_stig.py +33 -0
  245. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  246. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  247. tests/regscale/integrations/commercial/test_wiz.py +1365 -0
  248. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  249. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  250. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  251. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  252. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  253. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  254. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  255. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  256. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  257. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  258. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  259. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  260. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  261. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  262. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  263. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  264. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  265. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  266. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  267. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  268. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  269. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  270. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  271. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  272. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  273. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  274. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
  275. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  276. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  277. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  278. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  279. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  280. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
  281. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
  282. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  283. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  284. tests/regscale/integrations/public/__init__.py +0 -0
  285. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  286. tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
  287. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  288. tests/regscale/integrations/public/test_alienvault.py +220 -0
  289. tests/regscale/integrations/public/test_cci.py +1053 -0
  290. tests/regscale/integrations/public/test_cisa.py +1021 -0
  291. tests/regscale/integrations/public/test_emass.py +518 -0
  292. tests/regscale/integrations/public/test_fedramp.py +1152 -0
  293. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  294. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  295. tests/regscale/integrations/public/test_oscal.py +453 -0
  296. tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
  297. tests/regscale/integrations/test_control_matcher.py +1421 -0
  298. tests/regscale/integrations/test_control_matching.py +155 -0
  299. tests/regscale/integrations/test_milestone_manager.py +408 -0
  300. tests/regscale/models/test_control_implementation.py +118 -3
  301. tests/regscale/models/test_form_field_value_integration.py +304 -0
  302. tests/regscale/models/test_issue.py +378 -1
  303. tests/regscale/models/test_module_integration.py +582 -0
  304. tests/regscale/models/test_tenable_integrations.py +811 -105
  305. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
  306. regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
  307. regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
  308. regscale/integrations/public/fedramp/parts_mapper.py +0 -107
  309. /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
  310. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  311. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
  312. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
  313. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
  314. {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,805 @@
1
+ """
2
+ Comprehensive unit tests for WizVulnerabilityIntegration scanner class.
3
+ Focuses on achieving 90%+ code coverage with emphasis on previously uncovered lines.
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ import time
9
+ import unittest
10
+ from datetime import datetime, timedelta
11
+ from unittest.mock import MagicMock, Mock, AsyncMock, patch, call
12
+
13
+ from regscale.integrations.commercial.wizv2.core.constants import WizVulnerabilityType
14
+ from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
15
+ from regscale.integrations.commercial.wizv2.variables import WizVariables
16
+ from regscale.integrations.variables import ScannerVariables
17
+ from regscale.models import regscale_models
18
+ from regscale.models.regscale_models.issue import IssueStatus
19
+ from tests.regscale.integrations.commercial.wizv2 import (
20
+ PROJECT_ID,
21
+ PLAN_ID,
22
+ asset_nodes,
23
+ vuln_nodes,
24
+ )
25
+
26
+ logger = logging.getLogger("regscale")
27
+
28
+
29
+ @patch("regscale.integrations.scanner_integration.ScannerIntegration.__init__", return_value=None)
30
+ class TestWizVulnerabilityIntegrationScanner(unittest.TestCase):
31
+ """Test class for WizVulnerabilityIntegration scanner methods."""
32
+
33
+ def setUp(self, mock_parent_init=None):
34
+ """Set up test fixtures."""
35
+ self.plan_id = PLAN_ID
36
+ self.project_id = PROJECT_ID
37
+
38
+ def _initialize_scanner_attributes(self, integration, plan_id=None):
39
+ """Initialize parent class attributes that would normally be set by ScannerIntegration.__init__."""
40
+ from regscale.core.app.application import Application
41
+ from regscale.core.app.utils.app_utils import create_progress_object
42
+ from regscale.utils.threading import ThreadSafeList, ThreadSafeDict
43
+
44
+ integration.app = Application()
45
+ integration.plan_id = plan_id if plan_id is not None else self.plan_id
46
+ integration.tenant_id = 1
47
+ integration.is_component = False
48
+ integration.parent_module = regscale_models.SecurityPlan.get_module_string()
49
+ integration.asset_progress = create_progress_object()
50
+ integration.finding_progress = create_progress_object()
51
+ integration.components_by_title = ThreadSafeDict()
52
+ integration.components_by_id = ThreadSafeDict()
53
+ integration.components = ThreadSafeList()
54
+ integration.errors = []
55
+ integration.asset_map_by_identifier = ThreadSafeDict()
56
+ integration.software_to_create = ThreadSafeList()
57
+ integration.software_to_update = ThreadSafeList()
58
+ integration.data_to_create = ThreadSafeList()
59
+ integration.data_to_update = ThreadSafeList()
60
+ integration.link_to_create = ThreadSafeList()
61
+ integration.link_to_update = ThreadSafeList()
62
+ integration.existing_issues_map = ThreadSafeDict()
63
+ integration.alerted_assets = set()
64
+ from datetime import datetime
65
+
66
+ integration.scan_date = datetime.now().strftime("%Y-%m-%d")
67
+
68
+ # ========================================
69
+ # Initialization and Configuration Tests
70
+ # ========================================
71
+
72
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
73
+ def test_init_sets_suppress_asset_not_found_errors(self, mock_authenticate, mock_parent_init):
74
+ """Test that initialization sets suppress_asset_not_found_errors to True."""
75
+ mock_authenticate.return_value = None
76
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
77
+ self.assertTrue(integration.suppress_asset_not_found_errors)
78
+ self.assertIsInstance(integration._missing_asset_types, set)
79
+
80
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
81
+ def test_get_query_types_with_filter_by(self, mock_authenticate, mock_parent_init):
82
+ """Test get_query_types with custom filter_by parameter."""
83
+ mock_authenticate.return_value = None
84
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
85
+ filter_by = {"severity": "HIGH"}
86
+ query_types = integration.get_query_types(self.project_id, filter_by=filter_by)
87
+ self.assertIsInstance(query_types, list)
88
+ self.assertGreater(len(query_types), 0)
89
+
90
+ # ========================================
91
+ # Fetch Findings Tests (Lines 158-170, 388-396)
92
+ # ========================================
93
+
94
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
95
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_async")
96
+ def test_fetch_findings_uses_async_by_default(self, mock_async, mock_authenticate, mock_parent_init):
97
+ """Test that fetch_findings uses async by default."""
98
+ mock_authenticate.return_value = None
99
+ mock_async.return_value = iter([])
100
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
101
+ list(integration.fetch_findings(wiz_project_id=self.project_id))
102
+ mock_async.assert_called_once()
103
+
104
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
105
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_sync")
106
+ def test_fetch_findings_uses_sync_when_requested(self, mock_sync, mock_authenticate, mock_parent_init):
107
+ """Test that fetch_findings uses sync when use_async=False."""
108
+ mock_authenticate.return_value = None
109
+ mock_sync.return_value = iter([])
110
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
111
+ list(integration.fetch_findings(wiz_project_id=self.project_id, use_async=False))
112
+ mock_sync.assert_called_once()
113
+
114
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
115
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_async")
116
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_sync")
117
+ def test_fetch_findings_fallback_on_async_error(self, mock_sync, mock_async, mock_authenticate, mock_parent_init):
118
+ """Test fetch_findings falls back to sync on async error (line 165)."""
119
+ mock_authenticate.return_value = None
120
+ mock_async.side_effect = Exception("Async error")
121
+ mock_sync.return_value = iter([])
122
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
123
+ list(integration.fetch_findings(wiz_project_id=self.project_id, use_async=True))
124
+ mock_sync.assert_called_once()
125
+
126
+ # ========================================
127
+ # Async Findings Fetch Tests (Lines 388-408)
128
+ # ========================================
129
+
130
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
131
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._validate_project_id")
132
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.get_query_types")
133
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._setup_authentication_headers")
134
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._execute_concurrent_queries")
135
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._should_fetch_fresh_data")
136
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._process_query_results")
137
+ def test_fetch_findings_async_success_with_missing_asset_types(
138
+ self,
139
+ mock_process,
140
+ mock_should_fetch,
141
+ mock_execute,
142
+ mock_headers,
143
+ mock_query_types,
144
+ mock_validate,
145
+ mock_authenticate,
146
+ mock_parent_init,
147
+ ):
148
+ """Test fetch_findings_async with missing asset types tracking (lines 399-404)."""
149
+ mock_authenticate.return_value = None
150
+ mock_validate.return_value = self.project_id
151
+ mock_query_types.return_value = [
152
+ {"type": WizVulnerabilityType.VULNERABILITY, "query": "test_query", "variables": {}}
153
+ ]
154
+ mock_headers.return_value = {"Authorization": "Bearer test"}
155
+ mock_execute.return_value = []
156
+ mock_should_fetch.return_value = True
157
+ mock_process.return_value = iter([])
158
+
159
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
160
+ self._initialize_scanner_attributes(integration)
161
+ integration._missing_asset_types = {"VM", "DATABASE"}
162
+ list(integration.fetch_findings_async(wiz_project_id=self.project_id))
163
+
164
+ # Verify missing asset types were tracked
165
+ self.assertEqual(integration._missing_asset_types, {"VM", "DATABASE"})
166
+
167
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
168
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration._validate_project_id")
169
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_findings_sync")
170
+ def test_fetch_findings_async_error_fallback(self, mock_sync, mock_validate, mock_authenticate, mock_parent_init):
171
+ """Test fetch_findings_async falls back to sync on error (lines 388-396)."""
172
+ mock_authenticate.return_value = None
173
+ mock_validate.side_effect = Exception("Validation error")
174
+ mock_sync.return_value = iter([])
175
+
176
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
177
+ list(integration.fetch_findings_async(wiz_project_id=self.project_id))
178
+
179
+ mock_sync.assert_called_once()
180
+
181
+ # ========================================
182
+ # Process Query Results Tests (Lines 264-273)
183
+ # ========================================
184
+
185
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
186
+ def test_process_query_results_with_error(self, mock_authenticate, mock_parent_init):
187
+ """Test _process_query_results handles errors properly (lines 264-266)."""
188
+ mock_authenticate.return_value = None
189
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
190
+ self._initialize_scanner_attributes(integration)
191
+
192
+ results = [("VULNERABILITY", [], Exception("Test error"))]
193
+ query_configs = [
194
+ {
195
+ "type": WizVulnerabilityType.VULNERABILITY,
196
+ "query": "test",
197
+ "file_path": "/tmp/test.json",
198
+ }
199
+ ]
200
+
201
+ findings = list(integration._process_query_results(results, query_configs, self.project_id, False))
202
+ self.assertEqual(len(findings), 0)
203
+
204
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
205
+ def test_process_query_results_missing_vulnerability_type(self, mock_authenticate, mock_parent_init):
206
+ """Test _process_query_results with missing vulnerability type (lines 271-273)."""
207
+ mock_authenticate.return_value = None
208
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
209
+ self._initialize_scanner_attributes(integration)
210
+
211
+ results = [("UNKNOWN_TYPE", [{"id": "test"}], None)]
212
+ query_configs = [
213
+ {
214
+ "type": WizVulnerabilityType.VULNERABILITY,
215
+ "query": "test",
216
+ "file_path": "/tmp/test.json",
217
+ }
218
+ ]
219
+
220
+ findings = list(integration._process_query_results(results, query_configs, self.project_id, False))
221
+ self.assertEqual(len(findings), 0)
222
+
223
+ # ========================================
224
+ # Cache Management Tests (Lines 230-241)
225
+ # ========================================
226
+
227
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
228
+ @patch("regscale.integrations.commercial.wizv2.scanner.run_async_queries")
229
+ def test_execute_concurrent_queries_fetches_fresh_data(self, mock_run_async, mock_authenticate, mock_parent_init):
230
+ """Test _execute_concurrent_queries fetches fresh data (lines 232-240)."""
231
+ mock_authenticate.return_value = None
232
+ mock_run_async.return_value = []
233
+
234
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
235
+ self._initialize_scanner_attributes(integration)
236
+ query_configs = [
237
+ {
238
+ "type": WizVulnerabilityType.VULNERABILITY,
239
+ "query": "test_query",
240
+ "variables": {},
241
+ "file_path": "/tmp/nonexistent.json",
242
+ }
243
+ ]
244
+ headers = {"Authorization": "Bearer test"}
245
+
246
+ with patch.object(integration, "_should_fetch_fresh_data", return_value=True):
247
+ integration._execute_concurrent_queries(query_configs, headers)
248
+
249
+ mock_run_async.assert_called_once()
250
+
251
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
252
+ @patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.load_cached_findings")
253
+ def test_execute_concurrent_queries_loads_cached_data(self, mock_load_cached, mock_authenticate, mock_parent_init):
254
+ """Test _execute_concurrent_queries loads cached data (line 241)."""
255
+ mock_authenticate.return_value = None
256
+ mock_load_cached.return_value = []
257
+
258
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
259
+ self._initialize_scanner_attributes(integration)
260
+ query_configs = [
261
+ {
262
+ "type": WizVulnerabilityType.VULNERABILITY,
263
+ "query": "test_query",
264
+ "variables": {},
265
+ "file_path": "/tmp/test.json",
266
+ }
267
+ ]
268
+ headers = {"Authorization": "Bearer test"}
269
+
270
+ with patch.object(integration, "_should_fetch_fresh_data", return_value=False):
271
+ integration._execute_concurrent_queries(query_configs, headers)
272
+
273
+ mock_load_cached.assert_called_once()
274
+
275
+ # ========================================
276
+ # Should Fetch Fresh Data Tests
277
+ # ========================================
278
+
279
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
280
+ @patch("os.path.exists")
281
+ def test_should_fetch_fresh_data_missing_file(self, mock_exists, mock_authenticate, mock_parent_init):
282
+ """Test _should_fetch_fresh_data returns True for missing file."""
283
+ mock_authenticate.return_value = None
284
+ mock_exists.return_value = False
285
+
286
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
287
+ query_configs = [{"file_path": "/tmp/missing.json"}]
288
+
289
+ result = integration._should_fetch_fresh_data(query_configs)
290
+ self.assertTrue(result)
291
+
292
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
293
+ @patch("os.path.exists")
294
+ @patch("os.path.getmtime")
295
+ def test_should_fetch_fresh_data_old_file(self, mock_getmtime, mock_exists, mock_authenticate, mock_parent_init):
296
+ """Test _should_fetch_fresh_data returns True for old file."""
297
+ mock_authenticate.return_value = None
298
+ mock_exists.return_value = True
299
+ # File modified 10 hours ago
300
+ mock_getmtime.return_value = time.time() - (10 * 3600)
301
+
302
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
303
+ query_configs = [{"file_path": "/tmp/old.json"}]
304
+
305
+ result = integration._should_fetch_fresh_data(query_configs)
306
+ self.assertTrue(result)
307
+
308
+ # ========================================
309
+ # Load Cached Data with Progress Tests
310
+ # ========================================
311
+
312
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
313
+ @patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.load_cached_findings")
314
+ def test_load_cached_data_with_progress(self, mock_load_cached, mock_authenticate, mock_parent_init):
315
+ """Test _load_cached_data_with_progress loads data correctly."""
316
+ mock_authenticate.return_value = None
317
+ mock_load_cached.return_value = [("VULNERABILITY", [{"id": "test"}], None)]
318
+
319
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
320
+ self._initialize_scanner_attributes(integration)
321
+ query_configs = [
322
+ {
323
+ "type": WizVulnerabilityType.VULNERABILITY,
324
+ "query": "test",
325
+ "file_path": "/tmp/test.json",
326
+ }
327
+ ]
328
+
329
+ results = integration._load_cached_data_with_progress(query_configs)
330
+ self.assertEqual(len(results), 1)
331
+ mock_load_cached.assert_called_once()
332
+
333
+ # ========================================
334
+ # Save Data to Cache Tests
335
+ # ========================================
336
+
337
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
338
+ @patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.save_json_file")
339
+ def test_save_data_to_cache_success(self, mock_save, mock_authenticate, mock_parent_init):
340
+ """Test _save_data_to_cache saves successfully."""
341
+ mock_authenticate.return_value = None
342
+ mock_save.return_value = True
343
+
344
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
345
+ nodes = [{"id": "test1"}, {"id": "test2"}]
346
+ integration._save_data_to_cache(nodes, "/tmp/cache.json")
347
+
348
+ mock_save.assert_called_once_with(nodes, "/tmp/cache.json", create_dir=True)
349
+
350
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
351
+ @patch("regscale.integrations.commercial.wizv2.scanner.FileOperations.save_json_file")
352
+ def test_save_data_to_cache_no_path(self, mock_save, mock_authenticate, mock_parent_init):
353
+ """Test _save_data_to_cache skips when no path provided."""
354
+ mock_authenticate.return_value = None
355
+
356
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
357
+ nodes = [{"id": "test1"}]
358
+ integration._save_data_to_cache(nodes, None)
359
+
360
+ mock_save.assert_not_called()
361
+
362
+ # ========================================
363
+ # Parse Findings Tests (Lines 744-748)
364
+ # ========================================
365
+
366
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
367
+ def test_parse_findings_all_filtered_by_severity(self, mock_authenticate, mock_parent_init):
368
+ """Test parse_findings when all findings are filtered by severity (lines 744-748)."""
369
+ mock_authenticate.return_value = None
370
+
371
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
372
+ self._initialize_scanner_attributes(integration)
373
+ integration.app.config["scanners"] = {"wiz": {"minimumSeverity": "critical"}}
374
+
375
+ # Create nodes with only LOW severity
376
+ nodes = [
377
+ {"id": "test1", "severity": "LOW", "status": "OPEN"},
378
+ {"id": "test2", "severity": "MEDIUM", "status": "OPEN"},
379
+ ]
380
+
381
+ findings = list(integration.parse_findings(nodes, WizVulnerabilityType.VULNERABILITY))
382
+ self.assertEqual(len(findings), 0)
383
+
384
+ # ========================================
385
+ # Consolidation Tests
386
+ # ========================================
387
+
388
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
389
+ def test_should_apply_consolidation_for_data_finding(self, mock_authenticate, mock_parent_init):
390
+ """Test _should_apply_consolidation for DATA_FINDING type."""
391
+ mock_authenticate.return_value = None
392
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
393
+
394
+ result = integration._should_apply_consolidation(WizVulnerabilityType.DATA_FINDING)
395
+ self.assertTrue(result)
396
+
397
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
398
+ def test_should_not_apply_consolidation_for_secrets(self, mock_authenticate, mock_parent_init):
399
+ """Test _should_apply_consolidation returns False for SECRET_FINDING."""
400
+ mock_authenticate.return_value = None
401
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
402
+
403
+ result = integration._should_apply_consolidation(WizVulnerabilityType.SECRET_FINDING)
404
+ self.assertFalse(result)
405
+
406
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
407
+ def test_group_findings_for_consolidation(self, mock_authenticate, mock_parent_init):
408
+ """Test _group_findings_for_consolidation groups correctly."""
409
+ mock_authenticate.return_value = None
410
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
411
+
412
+ nodes = [
413
+ {"sourceRule": {"name": "Rule1"}, "entitySnapshot": {"providerId": "provider1"}},
414
+ {"sourceRule": {"name": "Rule1"}, "entitySnapshot": {"providerId": "provider1"}},
415
+ {"sourceRule": {"name": "Rule2"}, "entitySnapshot": {"providerId": "provider2"}},
416
+ ]
417
+
418
+ groups = integration._group_findings_for_consolidation(nodes)
419
+ self.assertIsInstance(groups, dict)
420
+ # Should have 2 groups: Rule1|provider1 and Rule2|provider2
421
+ self.assertGreaterEqual(len(groups), 2)
422
+
423
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
424
+ def test_create_consolidated_scanner_finding(self, mock_authenticate, mock_parent_init):
425
+ """Test _create_consolidated_scanner_finding creates consolidated finding."""
426
+ mock_authenticate.return_value = None
427
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
428
+ self._initialize_scanner_attributes(integration)
429
+
430
+ nodes = [
431
+ {
432
+ "id": "finding1",
433
+ "name": "CVE-2024-1234",
434
+ "severity": "HIGH",
435
+ "status": "OPEN",
436
+ "vulnerableAsset": {"id": "asset1", "name": "Asset 1", "providerUniqueId": "provider1"},
437
+ },
438
+ {
439
+ "id": "finding2",
440
+ "name": "CVE-2024-1234",
441
+ "severity": "HIGH",
442
+ "status": "OPEN",
443
+ "vulnerableAsset": {"id": "asset2", "name": "Asset 2", "providerUniqueId": "provider2"},
444
+ },
445
+ ]
446
+
447
+ result = integration._create_consolidated_scanner_finding(nodes, WizVulnerabilityType.VULNERABILITY)
448
+ self.assertIsNotNone(result)
449
+
450
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
451
+ def test_create_consolidated_scanner_finding_no_asset_ids(self, mock_authenticate, mock_parent_init):
452
+ """Test _create_consolidated_scanner_finding falls back when no asset IDs."""
453
+ mock_authenticate.return_value = None
454
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
455
+
456
+ nodes = [
457
+ {
458
+ "id": "finding1",
459
+ "name": "CVE-2024-1234",
460
+ "severity": "HIGH",
461
+ "status": "OPEN",
462
+ }
463
+ ]
464
+
465
+ with patch.object(integration, "get_asset_id_from_node", return_value=None):
466
+ with patch.object(integration, "parse_finding", return_value=Mock()) as mock_parse:
467
+ consolidated_finding = integration._create_consolidated_scanner_finding(
468
+ nodes, WizVulnerabilityType.VULNERABILITY
469
+ )
470
+ self.assertIsNotNone(consolidated_finding)
471
+ mock_parse.assert_called_once()
472
+
473
+ # ========================================
474
+ # Determine Grouping Scope Tests
475
+ # ========================================
476
+
477
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
478
+ def test_determine_grouping_scope_database(self, mock_authenticate, mock_parent_init):
479
+ """Test _determine_grouping_scope for database resources."""
480
+ mock_authenticate.return_value = None
481
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
482
+
483
+ provider_id = "/subscriptions/abc/resourceGroups/rg1/providers/Microsoft.Sql/servers/server1/databases/db1"
484
+ result = integration._determine_grouping_scope(provider_id, "Database Rule")
485
+
486
+ self.assertEqual(result, "/subscriptions/abc/resourceGroups/rg1/providers/Microsoft.Sql/servers/server1")
487
+
488
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
489
+ def test_determine_grouping_scope_app_configuration(self, mock_authenticate, mock_parent_init):
490
+ """Test _determine_grouping_scope for app configuration resources."""
491
+ mock_authenticate.return_value = None
492
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
493
+
494
+ provider_id = (
495
+ "/subscriptions/abc/resourcegroups/rg1/providers/microsoft.appconfiguration/configurationstores/store1"
496
+ )
497
+ result = integration._determine_grouping_scope(provider_id, "App Configuration Rule")
498
+
499
+ self.assertIn("resourcegroups/rg1", result)
500
+
501
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
502
+ def test_determine_grouping_scope_default(self, mock_authenticate, mock_parent_init):
503
+ """Test _determine_grouping_scope default behavior."""
504
+ mock_authenticate.return_value = None
505
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
506
+
507
+ provider_id = "/subscriptions/abc/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1"
508
+ result = integration._determine_grouping_scope(provider_id, "Other Rule")
509
+
510
+ self.assertEqual(result, provider_id)
511
+
512
+ # ========================================
513
+ # Get Rule Name from Node Tests
514
+ # ========================================
515
+
516
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
517
+ def test_get_rule_name_from_node_with_source_rule(self, mock_authenticate, mock_parent_init):
518
+ """Test _get_rule_name_from_node extracts from sourceRule."""
519
+ mock_authenticate.return_value = None
520
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
521
+
522
+ node = {"sourceRule": {"name": "Test Rule"}}
523
+ result = integration._get_rule_name_from_node(node)
524
+ self.assertEqual(result, "Test Rule")
525
+
526
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
527
+ def test_get_rule_name_from_node_fallback_to_name(self, mock_authenticate, mock_parent_init):
528
+ """Test _get_rule_name_from_node falls back to name field."""
529
+ mock_authenticate.return_value = None
530
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
531
+
532
+ node = {"name": "Fallback Name"}
533
+ result = integration._get_rule_name_from_node(node)
534
+ self.assertEqual(result, "Fallback Name")
535
+
536
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
537
+ def test_get_rule_name_from_node_fallback_to_title(self, mock_authenticate, mock_parent_init):
538
+ """Test _get_rule_name_from_node falls back to title field."""
539
+ mock_authenticate.return_value = None
540
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
541
+
542
+ node = {"title": "Title Name"}
543
+ result = integration._get_rule_name_from_node(node)
544
+ self.assertEqual(result, "Title Name")
545
+
546
+ # ========================================
547
+ # Get Provider ID from Node Tests
548
+ # ========================================
549
+
550
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
551
+ def test_get_provider_id_from_node_entity_snapshot(self, mock_authenticate, mock_parent_init):
552
+ """Test _get_provider_id_from_node with entitySnapshot."""
553
+ mock_authenticate.return_value = None
554
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
555
+
556
+ node = {"entitySnapshot": {"providerId": "provider-123"}}
557
+ result = integration._get_provider_id_from_node(node)
558
+ self.assertEqual(result, "provider-123")
559
+
560
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
561
+ def test_get_provider_id_from_node_vulnerable_asset(self, mock_authenticate, mock_parent_init):
562
+ """Test _get_provider_id_from_node with vulnerableAsset."""
563
+ mock_authenticate.return_value = None
564
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
565
+
566
+ node = {"vulnerableAsset": {"providerId": "provider-456"}}
567
+ result = integration._get_provider_id_from_node(node)
568
+ self.assertEqual(result, "provider-456")
569
+
570
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
571
+ def test_get_provider_id_from_node_fallback_to_asset_id(self, mock_authenticate, mock_parent_init):
572
+ """Test _get_provider_id_from_node falls back to asset ID."""
573
+ mock_authenticate.return_value = None
574
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
575
+
576
+ node = {"vulnerableAsset": {"id": "asset-789"}}
577
+ result = integration._get_provider_id_from_node(node)
578
+ self.assertEqual(result, "asset-789")
579
+
580
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
581
+ def test_get_provider_id_from_node_empty(self, mock_authenticate, mock_parent_init):
582
+ """Test _get_provider_id_from_node returns empty string when not found."""
583
+ mock_authenticate.return_value = None
584
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
585
+
586
+ node = {"someOtherField": "value"}
587
+ result = integration._get_provider_id_from_node(node)
588
+ self.assertEqual(result, "")
589
+
590
+ # ========================================
591
+ # Get Asset ID from Node Tests
592
+ # ========================================
593
+
594
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
595
+ def test_get_asset_id_from_node_configuration_finding(self, mock_authenticate, mock_parent_init):
596
+ """Test get_asset_id_from_node for CONFIGURATION type."""
597
+ mock_authenticate.return_value = None
598
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
599
+
600
+ node = {"resource": {"id": "resource-123"}}
601
+ result = integration.get_asset_id_from_node(node, WizVulnerabilityType.CONFIGURATION)
602
+ self.assertEqual(result, "resource-123")
603
+
604
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
605
+ def test_get_asset_id_from_node_issue_type(self, mock_authenticate, mock_parent_init):
606
+ """Test get_asset_id_from_node for ISSUE type."""
607
+ mock_authenticate.return_value = None
608
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
609
+
610
+ node = {"entitySnapshot": {"id": "entity-456"}}
611
+ result = integration.get_asset_id_from_node(node, WizVulnerabilityType.ISSUE)
612
+ self.assertEqual(result, "entity-456")
613
+
614
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
615
+ def test_get_asset_id_from_node_with_fallback(self, mock_authenticate, mock_parent_init):
616
+ """Test get_asset_id_from_node uses fallback when primary key not found."""
617
+ mock_authenticate.return_value = None
618
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
619
+
620
+ # Node doesn't have vulnerableAsset, but has resource
621
+ node = {"resource": {"id": "fallback-resource"}}
622
+ result = integration.get_asset_id_from_node(node, WizVulnerabilityType.VULNERABILITY)
623
+ self.assertEqual(result, "fallback-resource")
624
+
625
+ # ========================================
626
+ # Get Provider Unique ID from Node Tests
627
+ # ========================================
628
+
629
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
630
+ def test_get_provider_unique_id_from_node_issue_type(self, mock_authenticate, mock_parent_init):
631
+ """Test get_provider_unique_id_from_node for ISSUE type."""
632
+ mock_authenticate.return_value = None
633
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
634
+
635
+ node = {"entitySnapshot": {"providerId": "provider-issue-123"}}
636
+ result = integration.get_provider_unique_id_from_node(node, WizVulnerabilityType.ISSUE)
637
+ self.assertEqual(result, "provider-issue-123")
638
+
639
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
640
+ def test_get_provider_unique_id_from_node_issue_type_fallback(self, mock_authenticate, mock_parent_init):
641
+ """Test get_provider_unique_id_from_node for ISSUE type with fallback."""
642
+ mock_authenticate.return_value = None
643
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
644
+
645
+ node = {"entitySnapshot": {"name": "issue-name"}}
646
+ result = integration.get_provider_unique_id_from_node(node, WizVulnerabilityType.ISSUE)
647
+ self.assertEqual(result, "issue-name")
648
+
649
+ # ========================================
650
+ # Parse Finding Tests
651
+ # ========================================
652
+
653
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
654
+ def test_parse_finding_configuration_type(self, mock_authenticate, mock_parent_init):
655
+ """Test parse_finding routes to generic parsing for CONFIGURATION type."""
656
+ mock_authenticate.return_value = None
657
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
658
+
659
+ node = {
660
+ "id": "config-1",
661
+ "name": "Configuration Finding",
662
+ "severity": "HIGH",
663
+ "status": "OPEN",
664
+ "resource": {"id": "resource-1"},
665
+ }
666
+
667
+ with patch.object(integration, "_parse_generic_finding") as mock_parse:
668
+ mock_parse.return_value = Mock()
669
+ finding = integration.parse_finding(node, WizVulnerabilityType.CONFIGURATION)
670
+ self.assertIsNotNone(finding)
671
+ mock_parse.assert_called_once()
672
+
673
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
674
+ def test_parse_finding_error_handling(self, mock_authenticate, mock_parent_init):
675
+ """Test parse_finding handles errors gracefully."""
676
+ mock_authenticate.return_value = None
677
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
678
+
679
+ node = {"id": "error-node"}
680
+
681
+ with patch.object(integration, "_parse_generic_finding", side_effect=KeyError("Missing key")):
682
+ finding = integration.parse_finding(node, WizVulnerabilityType.VULNERABILITY)
683
+ self.assertIsNone(finding)
684
+
685
+ # ========================================
686
+ # Severity Filtering Tests
687
+ # ========================================
688
+
689
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
690
+ def test_should_process_finding_by_severity_unknown_severity(self, mock_authenticate, mock_parent_init):
691
+ """Test should_process_finding_by_severity with unknown severity defaults to processing."""
692
+ mock_authenticate.return_value = None
693
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
694
+ self._initialize_scanner_attributes(integration)
695
+ integration.app.config["scanners"] = {"wiz": {"minimumSeverity": "low"}}
696
+
697
+ result = integration.should_process_finding_by_severity("UNKNOWN_SEVERITY")
698
+ self.assertTrue(result)
699
+
700
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
701
+ def test_should_process_finding_by_severity_value_error(self, mock_authenticate, mock_parent_init):
702
+ """Test should_process_finding_by_severity handles ValueError gracefully."""
703
+ mock_authenticate.return_value = None
704
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
705
+ self._initialize_scanner_attributes(integration)
706
+ integration.app.config["scanners"] = {"wiz": {"minimumSeverity": "invalid_severity"}}
707
+
708
+ result = integration.should_process_finding_by_severity("HIGH")
709
+ # Should default to processing when config is invalid
710
+ self.assertTrue(result)
711
+
712
+ # ========================================
713
+ # Finding Data Extraction Tests
714
+ # ========================================
715
+
716
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
717
+ def test_get_secret_finding_data(self, mock_authenticate, mock_parent_init):
718
+ """Test _get_secret_finding_data extracts data correctly."""
719
+ mock_authenticate.return_value = None
720
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
721
+
722
+ node = {
723
+ "type": "AWS_SECRET_KEY",
724
+ "resource": {"name": "test-resource"},
725
+ "confidence": "High",
726
+ "isEncrypted": False,
727
+ "isManaged": True,
728
+ "rule": {"name": "AWS Secret Detection"},
729
+ }
730
+
731
+ result = integration._get_secret_finding_data(node)
732
+ self.assertEqual(result["category"], "Wiz Secret Detection")
733
+ self.assertIn("AWS_SECRET_KEY", result["title"])
734
+ self.assertIn("Confidence: High", result["description"])
735
+
736
+ # ========================================
737
+ # Status Mapping Tests
738
+ # ========================================
739
+
740
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
741
+ def test_map_wiz_status_active(self, mock_authenticate, mock_parent_init):
742
+ """Test map_wiz_status for Active status."""
743
+ mock_authenticate.return_value = None
744
+ status = WizVulnerabilityIntegration.map_wiz_status("Active")
745
+ self.assertEqual(status, regscale_models.AssetStatus.Active)
746
+
747
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
748
+ def test_map_wiz_status_inactive(self, mock_authenticate, mock_parent_init):
749
+ """Test map_wiz_status for Inactive status."""
750
+ mock_authenticate.return_value = None
751
+ status = WizVulnerabilityIntegration.map_wiz_status("Inactive")
752
+ self.assertEqual(status, regscale_models.AssetStatus.Inactive)
753
+
754
+ # ========================================
755
+ # Process Comments Tests
756
+ # ========================================
757
+
758
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
759
+ def test_process_comments_empty_edges(self, mock_authenticate, mock_parent_init):
760
+ """Test process_comments with empty edges."""
761
+ mock_authenticate.return_value = None
762
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
763
+
764
+ comments_dict = {"comments": {"edges": []}}
765
+ result = integration.process_comments(comments_dict)
766
+ self.assertIsNone(result)
767
+
768
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
769
+ def test_process_comments_with_data(self, mock_authenticate, mock_parent_init):
770
+ """Test process_comments with comment data."""
771
+ mock_authenticate.return_value = None
772
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
773
+
774
+ comments_dict = {
775
+ "comments": {
776
+ "edges": [
777
+ {"node": {"author": {"name": "John Doe"}, "body": "Comment 1"}},
778
+ {"node": {"author": {"name": "Jane Smith"}, "body": "Comment 2"}},
779
+ ]
780
+ }
781
+ }
782
+ result = integration.process_comments(comments_dict)
783
+ self.assertIn("John Doe: Comment 1", result)
784
+ self.assertIn("Jane Smith: Comment 2", result)
785
+
786
+ # ========================================
787
+ # Integration Tests with Real Data
788
+ # ========================================
789
+
790
+ @patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
791
+ def test_parse_findings_integration_with_consolidation(self, mock_authenticate, mock_parent_init):
792
+ """Integration test for parse_findings with consolidation."""
793
+ mock_authenticate.return_value = None
794
+ integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
795
+ self._initialize_scanner_attributes(integration)
796
+
797
+ # Use real test data
798
+ findings = list(integration.parse_findings(vuln_nodes, WizVulnerabilityType.VULNERABILITY))
799
+
800
+ # Should have processed some findings
801
+ self.assertGreater(len(findings), 0)
802
+
803
+
804
+ if __name__ == "__main__":
805
+ unittest.main()