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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/api.py +5 -2
- regscale/core/app/application.py +36 -6
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/evidence.py +727 -204
- regscale/core/app/internal/login.py +4 -2
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +86 -12
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/core/login.py +21 -4
- regscale/core/utils/async_graphql_client.py +363 -0
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/code_gen.py +109 -24
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +30 -2
- regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
- regscale/integrations/commercial/aws/cli.py +3107 -54
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
- regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
- regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +1072 -205
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/jira.py +489 -153
- regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
- regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
- regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
- regscale/integrations/commercial/qualys/__init__.py +167 -68
- regscale/integrations/commercial/qualys/scanner.py +305 -39
- regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
- regscale/integrations/commercial/sicura/api.py +79 -42
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +83 -44
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +133 -16
- regscale/integrations/commercial/synqly/edr.py +2 -8
- regscale/integrations/commercial/synqly/query_builder.py +536 -0
- regscale/integrations/commercial/synqly/ticketing.py +27 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +146 -5
- regscale/integrations/commercial/tenablev2/scanner.py +1 -3
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +191 -76
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +776 -28
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +1031 -441
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +1036 -151
- regscale/integrations/control_matcher.py +432 -0
- regscale/integrations/due_date_handler.py +333 -0
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +14 -0
- regscale/integrations/public/cci_importer.py +834 -0
- regscale/integrations/public/csam/__init__.py +0 -0
- regscale/integrations/public/csam/csam.py +938 -0
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +77 -6
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam/scanner.py +75 -7
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +1961 -430
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/aqua.py +2 -2
- regscale/models/integration_models/cisa_kev_data.json +805 -11
- regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
- regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
- regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +4 -2
- regscale/models/regscale_models/__init__.py +7 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/catalog.py +1 -1
- regscale/models/regscale_models/compliance_settings.py +251 -1
- regscale/models/regscale_models/component.py +1 -0
- regscale/models/regscale_models/control_implementation.py +236 -41
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/form_field_value.py +5 -3
- regscale/models/regscale_models/inheritance.py +44 -0
- regscale/models/regscale_models/issue.py +301 -102
- regscale/models/regscale_models/milestone.py +33 -14
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +310 -73
- regscale/models/regscale_models/security_plan.py +4 -2
- regscale/models/regscale_models/vulnerability.py +3 -3
- regscale/regscale.py +25 -4
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- regscale/validation/record.py +23 -1
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
- tests/core/__init__.py +0 -0
- tests/core/utils/__init__.py +0 -0
- tests/core/utils/test_async_graphql_client.py +472 -0
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
- tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
- tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3742 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +349 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +1053 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +1152 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
- tests/regscale/integrations/test_control_matcher.py +1421 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_issue.py +378 -1
- tests/regscale/models/test_module_integration.py +582 -0
- tests/regscale/models/test_tenable_integrations.py +811 -105
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
- regscale/integrations/public/fedramp/parts_mapper.py +0 -107
- /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
- {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()
|