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,375 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Unit tests for ControlComplianceAnalyzer class.
5
+
6
+ Tests the control pass/fail determination logic based on AWS Audit Manager evidence insights.
7
+ """
8
+
9
+ import unittest
10
+ from unittest.mock import MagicMock, patch
11
+
12
+ from regscale.integrations.commercial.aws.control_compliance_analyzer import (
13
+ ComplianceAnalysis,
14
+ ComplianceStatus,
15
+ ControlComplianceAnalyzer,
16
+ EvidenceInsight,
17
+ EvidenceType,
18
+ )
19
+
20
+
21
+ class TestControlComplianceAnalyzer(unittest.TestCase):
22
+ """Test cases for ControlComplianceAnalyzer."""
23
+
24
+ def setUp(self):
25
+ """Set up test fixtures."""
26
+ self.control_id = "AC-2"
27
+ self.analyzer = ControlComplianceAnalyzer(self.control_id)
28
+
29
+ def test_initialization(self):
30
+ """Test analyzer initialization."""
31
+ self.assertEqual(self.analyzer.control_id, "AC-2")
32
+ self.assertEqual(len(self.analyzer.evidence_insights), 0)
33
+ self.assertEqual(self.analyzer._compliant_count, 0)
34
+ self.assertEqual(self.analyzer._noncompliant_count, 0)
35
+ self.assertEqual(self.analyzer._inconclusive_count, 0)
36
+ self.assertEqual(self.analyzer._not_applicable_count, 0)
37
+
38
+ def test_pass_status_all_compliant(self):
39
+ """Test PASS status when all evidence is compliant."""
40
+ # Add compliant evidence
41
+ evidence_data = {
42
+ "id": "evidence-1",
43
+ "dataSource": "AWS Security Hub",
44
+ "complianceCheck": "PASS",
45
+ "time": "2025-01-15T10:00:00Z",
46
+ }
47
+ self.analyzer.add_evidence_insight(evidence_data)
48
+
49
+ evidence_data2 = {
50
+ "id": "evidence-2",
51
+ "dataSource": "AWS Config",
52
+ "complianceCheck": "COMPLIANT",
53
+ "time": "2025-01-15T10:01:00Z",
54
+ }
55
+ self.analyzer.add_evidence_insight(evidence_data2)
56
+
57
+ status, details = self.analyzer.determine_control_status()
58
+
59
+ self.assertEqual(status, "PASS")
60
+ self.assertEqual(details["reason"], "All evidence indicates compliance")
61
+ self.assertEqual(details["compliant_count"], 2)
62
+ # Non-compliant count not included in PASS status details
63
+
64
+ def test_fail_status_any_noncompliant(self):
65
+ """Test FAIL status when any evidence is non-compliant."""
66
+ # Add mixed evidence
67
+ compliant_evidence = {
68
+ "id": "evidence-1",
69
+ "dataSource": "AWS Security Hub",
70
+ "complianceCheck": "PASS",
71
+ }
72
+ self.analyzer.add_evidence_insight(compliant_evidence)
73
+
74
+ noncompliant_evidence = {
75
+ "id": "evidence-2",
76
+ "dataSource": "AWS Config",
77
+ "complianceCheck": "NON_COMPLIANT",
78
+ }
79
+ self.analyzer.add_evidence_insight(noncompliant_evidence)
80
+
81
+ status, details = self.analyzer.determine_control_status()
82
+
83
+ self.assertEqual(status, "FAIL")
84
+ self.assertEqual(details["reason"], "Evidence indicates non-compliance")
85
+ self.assertEqual(details["noncompliant_count"], 1)
86
+ self.assertEqual(details["compliant_count"], 1)
87
+
88
+ def test_inconclusive_status_only_inconclusive(self):
89
+ """Test INCONCLUSIVE status when only inconclusive evidence exists."""
90
+ # Add inconclusive evidence
91
+ evidence_data = {
92
+ "id": "evidence-1",
93
+ "dataSource": "AWS CloudTrail",
94
+ "complianceCheck": "", # Empty means inconclusive
95
+ }
96
+ self.analyzer.add_evidence_insight(evidence_data)
97
+
98
+ evidence_data2 = {
99
+ "id": "evidence-2",
100
+ "dataSource": "Manual",
101
+ "complianceCheck": "UNKNOWN",
102
+ }
103
+ self.analyzer.add_evidence_insight(evidence_data2)
104
+
105
+ status, details = self.analyzer.determine_control_status()
106
+
107
+ self.assertEqual(status, "INCONCLUSIVE")
108
+ self.assertEqual(details["reason"], "Only inconclusive evidence available")
109
+ self.assertEqual(details["inconclusive_count"], 2)
110
+
111
+ def test_no_data_status(self):
112
+ """Test NO_DATA status when no evidence is available."""
113
+ status, details = self.analyzer.determine_control_status()
114
+
115
+ self.assertEqual(status, "NO_DATA")
116
+ self.assertEqual(details["reason"], "No evidence available for assessment")
117
+ self.assertEqual(details["total_evidence_checked"], 0)
118
+
119
+ def test_not_applicable_status(self):
120
+ """Test NOT_APPLICABLE status when all evidence is not applicable."""
121
+ # Add not applicable evidence
122
+ evidence_data = {
123
+ "id": "evidence-1",
124
+ "dataSource": "AWS Config",
125
+ "complianceCheck": "NOT_APPLICABLE",
126
+ }
127
+ self.analyzer.add_evidence_insight(evidence_data)
128
+
129
+ evidence_data2 = {
130
+ "id": "evidence-2",
131
+ "dataSource": "AWS Security Hub",
132
+ "complianceCheck": "N/A",
133
+ }
134
+ self.analyzer.add_evidence_insight(evidence_data2)
135
+
136
+ status, details = self.analyzer.determine_control_status()
137
+
138
+ self.assertEqual(status, "NOT_APPLICABLE")
139
+ self.assertEqual(details["reason"], "Evidence is not applicable to this control")
140
+ self.assertEqual(details["not_applicable_count"], 2)
141
+
142
+ def test_compliance_normalization(self):
143
+ """Test compliance check value normalization."""
144
+ test_cases = [
145
+ ("PASS", "COMPLIANT"),
146
+ ("pass", "COMPLIANT"),
147
+ ("PASSED", "COMPLIANT"),
148
+ ("SUCCESS", "COMPLIANT"),
149
+ ("COMPLIANT", "COMPLIANT"),
150
+ ("FAIL", "NON_COMPLIANT"),
151
+ ("fail", "NON_COMPLIANT"),
152
+ ("FAILED", "NON_COMPLIANT"),
153
+ ("NON_COMPLIANT", "NON_COMPLIANT"),
154
+ ("NON-COMPLIANT", "NON_COMPLIANT"),
155
+ ("Non-compliant", "NON_COMPLIANT"),
156
+ ("NOT_APPLICABLE", "NOT_APPLICABLE"),
157
+ ("NOT-APPLICABLE", "NOT_APPLICABLE"),
158
+ ("N/A", "NOT_APPLICABLE"),
159
+ ("NA", "NOT_APPLICABLE"),
160
+ ("", "INCONCLUSIVE"),
161
+ ("UNKNOWN", "INCONCLUSIVE"),
162
+ ("SOMETHING_ELSE", "INCONCLUSIVE"),
163
+ ]
164
+
165
+ for input_value, expected in test_cases:
166
+ result = self.analyzer._normalize_compliance_check(input_value)
167
+ self.assertEqual(result, expected, f"Failed to normalize '{input_value}' to '{expected}', got '{result}'")
168
+
169
+ def test_add_evidence_from_insights_api(self):
170
+ """Test adding evidence from AWS Audit Manager Control Insights API."""
171
+ insights_data = {
172
+ "evidenceInsights": {
173
+ "compliantEvidenceCount": 15,
174
+ "noncompliantEvidenceCount": 2,
175
+ "inconclusiveEvidenceCount": 5,
176
+ }
177
+ }
178
+
179
+ self.analyzer.add_evidence_from_insights_api(insights_data)
180
+
181
+ # Check counts
182
+ self.assertEqual(self.analyzer._compliant_count, 15)
183
+ self.assertEqual(self.analyzer._noncompliant_count, 2)
184
+ self.assertEqual(self.analyzer._inconclusive_count, 5)
185
+
186
+ # Check status determination (should be FAIL due to non-compliant evidence)
187
+ status, details = self.analyzer.determine_control_status()
188
+ self.assertEqual(status, "FAIL")
189
+
190
+ def test_compliance_score_calculation(self):
191
+ """Test compliance score calculation."""
192
+ # All compliant = score 1.0
193
+ self.analyzer._compliant_count = 10
194
+ self.analyzer._noncompliant_count = 0
195
+ self.analyzer._inconclusive_count = 0
196
+ score = self.analyzer.get_compliance_score()
197
+ self.assertEqual(score, 1.0)
198
+
199
+ # Any non-compliant = score 0.0
200
+ self.analyzer._noncompliant_count = 1
201
+ score = self.analyzer.get_compliance_score()
202
+ self.assertEqual(score, 0.0)
203
+
204
+ # Only inconclusive = score 0.5
205
+ self.analyzer._compliant_count = 0
206
+ self.analyzer._noncompliant_count = 0
207
+ self.analyzer._inconclusive_count = 5
208
+ score = self.analyzer.get_compliance_score()
209
+ self.assertEqual(score, 0.5)
210
+
211
+ # Mixed compliant and inconclusive
212
+ self.analyzer._compliant_count = 7
213
+ self.analyzer._noncompliant_count = 0
214
+ self.analyzer._inconclusive_count = 3
215
+ score = self.analyzer.get_compliance_score()
216
+ self.assertEqual(score, 0.7)
217
+
218
+ def test_confidence_level_calculation(self):
219
+ """Test confidence level calculation."""
220
+ # Add evidence with different confidence levels
221
+ high_confidence_evidence = {
222
+ "id": "evidence-1",
223
+ "dataSource": "AWS Security Hub", # 0.95 confidence
224
+ "complianceCheck": "PASS",
225
+ }
226
+ self.analyzer.add_evidence_insight(high_confidence_evidence)
227
+
228
+ medium_confidence_evidence = {
229
+ "id": "evidence-2",
230
+ "dataSource": "AWS CloudTrail", # 0.75 confidence
231
+ "complianceCheck": "PASS",
232
+ }
233
+ self.analyzer.add_evidence_insight(medium_confidence_evidence)
234
+
235
+ low_confidence_evidence = {
236
+ "id": "evidence-3",
237
+ "dataSource": "Manual", # 0.60 confidence
238
+ "complianceCheck": "PASS",
239
+ }
240
+ self.analyzer.add_evidence_insight(low_confidence_evidence)
241
+
242
+ confidence = self.analyzer.get_confidence_level()
243
+
244
+ # Should be weighted average with quantity factor
245
+ # Average confidence: (0.95 + 0.75 + 0.60) / 3 = 0.766...
246
+ # Quantity factor: min(1.0, 3/10) = 0.3
247
+ # Final: (0.766 * 0.7) + (0.3 * 0.3) = 0.536 + 0.09 = 0.626
248
+ self.assertAlmostEqual(confidence, 0.626, places=2)
249
+
250
+ def test_get_compliance_analysis(self):
251
+ """Test getting comprehensive compliance analysis."""
252
+ # Add mixed evidence
253
+ evidence1 = {
254
+ "id": "evidence-1",
255
+ "dataSource": "AWS Security Hub",
256
+ "complianceCheck": "PASS",
257
+ }
258
+ self.analyzer.add_evidence_insight(evidence1)
259
+
260
+ evidence2 = {
261
+ "id": "evidence-2",
262
+ "dataSource": "AWS Config",
263
+ "complianceCheck": "COMPLIANT",
264
+ }
265
+ self.analyzer.add_evidence_insight(evidence2)
266
+
267
+ evidence3 = {
268
+ "id": "evidence-3",
269
+ "dataSource": "AWS CloudTrail",
270
+ "complianceCheck": "", # Inconclusive
271
+ }
272
+ self.analyzer.add_evidence_insight(evidence3)
273
+
274
+ analysis = self.analyzer.get_compliance_analysis()
275
+
276
+ self.assertIsInstance(analysis, ComplianceAnalysis)
277
+ self.assertEqual(analysis.control_id, "AC-2")
278
+ self.assertEqual(analysis.compliance_status, ComplianceStatus.PASS)
279
+ self.assertEqual(analysis.compliant_evidence_count, 2)
280
+ self.assertEqual(analysis.noncompliant_evidence_count, 0)
281
+ self.assertEqual(analysis.inconclusive_evidence_count, 1)
282
+ self.assertEqual(analysis.total_evidence_count, 3)
283
+ # Check that reason mentions inconclusive evidence
284
+ self.assertIn("some evidence inconclusive", analysis.reasoning)
285
+ self.assertIn("AWS Security Hub", analysis.evidence_sources)
286
+ self.assertIn("AWS Config", analysis.evidence_sources)
287
+ self.assertIn("AWS CloudTrail", analysis.evidence_sources)
288
+
289
+ def test_evidence_sources_tracking(self):
290
+ """Test tracking of unique evidence sources."""
291
+ # Add evidence from same source multiple times
292
+ for i in range(3):
293
+ evidence = {
294
+ "id": f"evidence-{i}",
295
+ "dataSource": "AWS Security Hub",
296
+ "complianceCheck": "PASS",
297
+ }
298
+ self.analyzer.add_evidence_insight(evidence)
299
+
300
+ # Add from different source
301
+ evidence = {
302
+ "id": "evidence-4",
303
+ "dataSource": "AWS Config",
304
+ "complianceCheck": "COMPLIANT",
305
+ }
306
+ self.analyzer.add_evidence_insight(evidence)
307
+
308
+ analysis = self.analyzer.get_compliance_analysis()
309
+
310
+ # Should only have 2 unique sources
311
+ self.assertEqual(len(analysis.evidence_sources), 2)
312
+ self.assertIn("AWS Security Hub", analysis.evidence_sources)
313
+ self.assertIn("AWS Config", analysis.evidence_sources)
314
+
315
+ def test_fail_priority_over_pass(self):
316
+ """Test that any failure overrides passing evidence."""
317
+ # Add 10 compliant evidence items
318
+ for i in range(10):
319
+ evidence = {
320
+ "id": f"compliant-{i}",
321
+ "dataSource": "AWS Security Hub",
322
+ "complianceCheck": "PASS",
323
+ }
324
+ self.analyzer.add_evidence_insight(evidence)
325
+
326
+ # Add 1 non-compliant evidence
327
+ fail_evidence = {
328
+ "id": "fail-1",
329
+ "dataSource": "AWS Config",
330
+ "complianceCheck": "NON_COMPLIANT",
331
+ }
332
+ self.analyzer.add_evidence_insight(fail_evidence)
333
+
334
+ status, details = self.analyzer.determine_control_status()
335
+
336
+ # Should be FAIL despite 10 passing evidences
337
+ self.assertEqual(status, "FAIL")
338
+ self.assertEqual(details["compliant_count"], 10)
339
+ self.assertEqual(details["noncompliant_count"], 1)
340
+
341
+ def test_mixed_evidence_with_not_applicable(self):
342
+ """Test mixed evidence including not applicable items."""
343
+ # Add various types of evidence
344
+ self.analyzer.add_evidence_insight({"id": "1", "dataSource": "AWS Security Hub", "complianceCheck": "PASS"})
345
+ self.analyzer.add_evidence_insight({"id": "2", "dataSource": "AWS Config", "complianceCheck": "NOT_APPLICABLE"})
346
+ self.analyzer.add_evidence_insight({"id": "3", "dataSource": "Manual", "complianceCheck": ""})
347
+
348
+ status, details = self.analyzer.determine_control_status()
349
+
350
+ # Should be PASS (compliant evidence with no failures)
351
+ self.assertEqual(status, "PASS")
352
+ self.assertEqual(details["compliant_count"], 1)
353
+ self.assertEqual(details["not_applicable_count"], 1)
354
+ self.assertEqual(details["inconclusive_count"], 1)
355
+
356
+ def test_evidence_with_resource_level_compliance(self):
357
+ """Test evidence with resource-level compliance information."""
358
+ evidence = {
359
+ "id": "evidence-1",
360
+ "dataSource": "AWS Config",
361
+ "complianceCheck": "NON_COMPLIANT",
362
+ "resourceArn": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0",
363
+ "attributes": {"ruleName": "ec2-instance-managed-by-systems-manager"},
364
+ }
365
+ self.analyzer.add_evidence_insight(evidence)
366
+
367
+ # Check that resource information is preserved
368
+ self.assertEqual(len(self.analyzer.evidence_insights), 1)
369
+ insight = self.analyzer.evidence_insights[0]
370
+ self.assertEqual(insight.resource_arn, "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0")
371
+ self.assertEqual(insight.attributes["ruleName"], "ec2-instance-managed-by-systems-manager")
372
+
373
+
374
+ if __name__ == "__main__":
375
+ unittest.main()
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Unit tests for AWS Audit Manager datetime parsing and comparison.
5
+ Tests that all timestamp formats are properly parsed as timezone-naive UTC datetimes.
6
+ """
7
+
8
+ import unittest
9
+ from datetime import datetime, timedelta, timezone
10
+ from unittest.mock import MagicMock, patch
11
+ from regscale.integrations.commercial.aws.audit_manager_compliance import AWSAuditManagerCompliance
12
+
13
+
14
+ class TestDateTimeParsing(unittest.TestCase):
15
+ """Test cases for datetime parsing and timezone handling."""
16
+
17
+ def setUp(self):
18
+ """Set up test fixtures."""
19
+ self.compliance = AWSAuditManagerCompliance(plan_id=50)
20
+
21
+ # Calculate yesterday's date range (same as in the actual code)
22
+ now_utc = datetime.now(timezone.utc).replace(tzinfo=None)
23
+ self.yesterday_start = (now_utc - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
24
+ self.yesterday_end = self.yesterday_start + timedelta(days=1)
25
+
26
+ def test_parse_iso_format_with_z(self):
27
+ """Test parsing ISO format with Z timezone indicator."""
28
+ timestamp = "2025-11-03T19:00:00Z"
29
+ result = self.compliance._parse_evidence_timestamp(timestamp)
30
+
31
+ self.assertIsNotNone(result)
32
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
33
+
34
+ # Should be able to compare with naive datetimes
35
+ try:
36
+ _ = self.yesterday_start <= result < self.yesterday_end
37
+ self.assertTrue(True, "Comparison should not raise exception")
38
+ except TypeError:
39
+ self.fail("Should be able to compare naive datetimes")
40
+
41
+ def test_parse_iso_format_with_positive_offset(self):
42
+ """Test parsing ISO format with positive timezone offset."""
43
+ timestamp = "2025-11-03T19:00:00+00:00"
44
+ result = self.compliance._parse_evidence_timestamp(timestamp)
45
+
46
+ self.assertIsNotNone(result)
47
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
48
+
49
+ # Should be able to compare with naive datetimes
50
+ try:
51
+ _ = self.yesterday_start <= result < self.yesterday_end
52
+ self.assertTrue(True, "Comparison should not raise exception")
53
+ except TypeError:
54
+ self.fail("Should be able to compare naive datetimes")
55
+
56
+ def test_parse_iso_format_with_negative_offset(self):
57
+ """Test parsing ISO format with negative timezone offset."""
58
+ timestamp = "2025-11-03T19:00:00-05:00"
59
+ result = self.compliance._parse_evidence_timestamp(timestamp)
60
+
61
+ self.assertIsNotNone(result)
62
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
63
+
64
+ # Time should be converted to UTC (19:00 -05:00 = 00:00 next day UTC)
65
+ expected_utc = datetime(2025, 11, 4, 0, 0, 0)
66
+ self.assertEqual(result, expected_utc)
67
+
68
+ # Should be able to compare with naive datetimes
69
+ try:
70
+ _ = self.yesterday_start <= result < self.yesterday_end
71
+ self.assertTrue(True, "Comparison should not raise exception")
72
+ except TypeError:
73
+ self.fail("Should be able to compare naive datetimes")
74
+
75
+ def test_parse_space_format_with_offset(self):
76
+ """Test parsing format with space separator and timezone offset."""
77
+ timestamp = "2025-11-03 19:00:00-05:00"
78
+ result = self.compliance._parse_evidence_timestamp(timestamp)
79
+
80
+ self.assertIsNotNone(result)
81
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
82
+
83
+ # Should be able to compare with naive datetimes
84
+ try:
85
+ _ = self.yesterday_start <= result < self.yesterday_end
86
+ self.assertTrue(True, "Comparison should not raise exception")
87
+ except TypeError:
88
+ self.fail("Should be able to compare naive datetimes")
89
+
90
+ def test_parse_simple_format(self):
91
+ """Test parsing simple datetime format without timezone."""
92
+ timestamp = "2025-11-03 19:00:00"
93
+ result = self.compliance._parse_evidence_timestamp(timestamp)
94
+
95
+ self.assertIsNotNone(result)
96
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
97
+
98
+ # Should be able to compare with naive datetimes
99
+ try:
100
+ _ = self.yesterday_start <= result < self.yesterday_end
101
+ self.assertTrue(True, "Comparison should not raise exception")
102
+ except TypeError:
103
+ self.fail("Should be able to compare naive datetimes")
104
+
105
+ def test_parse_with_microseconds(self):
106
+ """Test parsing datetime with microseconds."""
107
+ timestamp = "2025-11-03 19:00:00.123456"
108
+ result = self.compliance._parse_evidence_timestamp(timestamp)
109
+
110
+ self.assertIsNotNone(result)
111
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
112
+ self.assertEqual(result.microsecond, 123456)
113
+
114
+ def test_parse_datetime_object(self):
115
+ """Test handling datetime object as input."""
116
+ # Test with naive datetime
117
+ dt_naive = datetime(2025, 11, 3, 19, 0, 0)
118
+ result = self.compliance._parse_evidence_timestamp(dt_naive)
119
+
120
+ self.assertEqual(result, dt_naive)
121
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive")
122
+
123
+ # Test with aware datetime - should be converted to naive
124
+ dt_aware = datetime(2025, 11, 3, 19, 0, 0, tzinfo=timezone.utc)
125
+ result = self.compliance._parse_evidence_timestamp(dt_aware)
126
+
127
+ self.assertIsNotNone(result)
128
+ self.assertIsNone(result.tzinfo, "Result should be timezone-naive after conversion")
129
+
130
+ def test_parse_invalid_input(self):
131
+ """Test handling invalid input."""
132
+ # None input
133
+ result = self.compliance._parse_evidence_timestamp(None)
134
+ self.assertIsNone(result)
135
+
136
+ # Integer input
137
+ result = self.compliance._parse_evidence_timestamp(12345)
138
+ self.assertIsNone(result)
139
+
140
+ # Invalid string format
141
+ result = self.compliance._parse_evidence_timestamp("not-a-date")
142
+ self.assertIsNone(result)
143
+
144
+ def test_filter_evidence_by_date(self):
145
+ """Test filtering evidence items by date range."""
146
+ # Create test evidence items with different timestamps
147
+ evidence_items = [
148
+ {"id": "1", "time": "2025-11-03T10:00:00Z"}, # Yesterday morning
149
+ {"id": "2", "time": "2025-11-03T23:59:59Z"}, # Yesterday evening
150
+ {"id": "3", "time": "2025-11-04T00:00:01Z"}, # Today
151
+ {"id": "4", "time": "2025-11-02T23:59:59Z"}, # Day before yesterday
152
+ {"id": "5", "time": None}, # No timestamp
153
+ {"id": "6", "time": "invalid"}, # Invalid timestamp
154
+ ]
155
+
156
+ # Set up yesterday's range for Nov 3rd
157
+ yesterday_start = datetime(2025, 11, 3, 0, 0, 0)
158
+ yesterday_end = datetime(2025, 11, 4, 0, 0, 0)
159
+
160
+ filtered = self.compliance._filter_evidence_by_date(evidence_items, yesterday_start, yesterday_end)
161
+
162
+ # Should only include items 1 and 2 (yesterday's evidence)
163
+ self.assertEqual(len(filtered), 2)
164
+ self.assertEqual(filtered[0]["id"], "1")
165
+ self.assertEqual(filtered[1]["id"], "2")
166
+
167
+ def test_filter_evidence_with_timezone_offsets(self):
168
+ """Test filtering evidence with various timezone offsets."""
169
+ evidence_items = [
170
+ {"id": "1", "time": "2025-11-03T05:00:00-05:00"}, # 10:00 UTC - yesterday
171
+ {"id": "2", "time": "2025-11-03T20:00:00+01:00"}, # 19:00 UTC - yesterday
172
+ {"id": "3", "time": "2025-11-04T01:00:00+01:00"}, # 00:00 UTC - today
173
+ ]
174
+
175
+ yesterday_start = datetime(2025, 11, 3, 0, 0, 0)
176
+ yesterday_end = datetime(2025, 11, 4, 0, 0, 0)
177
+
178
+ filtered = self.compliance._filter_evidence_by_date(evidence_items, yesterday_start, yesterday_end)
179
+
180
+ # Items 1 and 2 are in yesterday's range in UTC
181
+ self.assertEqual(len(filtered), 2)
182
+ self.assertEqual(filtered[0]["id"], "1")
183
+ self.assertEqual(filtered[1]["id"], "2")
184
+
185
+ @patch("regscale.integrations.commercial.aws.audit_manager_compliance.logger")
186
+ def test_collect_evidence_with_mixed_timestamps(self, mock_logger):
187
+ """Test evidence collection with mixed timestamp formats."""
188
+ # Mock AWS client
189
+ self.compliance.audit_manager_client = MagicMock()
190
+
191
+ # Mock response with mixed timestamp formats
192
+ mock_response = {
193
+ "evidence": [
194
+ {"id": "ev-1", "time": "2025-11-03T10:00:00Z"},
195
+ {"id": "ev-2", "time": "2025-11-03T15:00:00-05:00"}, # 20:00 UTC
196
+ {"id": "ev-3", "time": datetime(2025, 11, 3, 18, 0, 0)}, # Naive datetime
197
+ {"id": "ev-4", "time": datetime(2025, 11, 3, 19, 0, 0, tzinfo=timezone.utc)}, # Aware datetime
198
+ ],
199
+ "nextToken": None,
200
+ }
201
+
202
+ self.compliance.client = MagicMock()
203
+ self.compliance.client.get_evidence_by_evidence_folder = MagicMock(return_value=mock_response)
204
+
205
+ evidence_items = []
206
+ self.compliance._collect_evidence_from_folder(
207
+ assessment_id="test-assessment",
208
+ control_set_id="test-control-set",
209
+ evidence_folder_id="test-folder",
210
+ evidence_items=evidence_items,
211
+ )
212
+
213
+ # All evidence items should be collected without errors
214
+ self.assertEqual(len(evidence_items), 4)
215
+
216
+ # Verify no TypeError was raised
217
+ self.assertFalse(
218
+ any("can't compare offset-naive and offset-aware" in str(call) for call in mock_logger.error.call_args_list)
219
+ )
220
+
221
+
222
+ if __name__ == "__main__":
223
+ unittest.main()