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,518 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Test eMASS Integration"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from click.testing import CliRunner
|
|
13
|
+
from openpyxl import Workbook
|
|
14
|
+
from openpyxl.reader.excel import load_workbook
|
|
15
|
+
from openpyxl.styles import PatternFill
|
|
16
|
+
from openpyxl.comments import Comment
|
|
17
|
+
import unittest.mock as mock
|
|
18
|
+
|
|
19
|
+
import regscale.integrations.public.emass as emass_mod
|
|
20
|
+
from regscale.integrations.public.emass import (
|
|
21
|
+
populate_assessment_results,
|
|
22
|
+
populate_emass_workbook,
|
|
23
|
+
import_emass_slcm_file,
|
|
24
|
+
determine_assessment_result,
|
|
25
|
+
map_finish_date,
|
|
26
|
+
map_ccis,
|
|
27
|
+
fetch_template_from_blob,
|
|
28
|
+
populate_emass_workbook,
|
|
29
|
+
SKIP_ROWS,
|
|
30
|
+
)
|
|
31
|
+
from tests import CLITestFixture
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestEmass(CLITestFixture):
|
|
35
|
+
"""Integration, unit, and CLI callback tests for the eMASS CLI integration logic."""
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(autouse=True)
|
|
38
|
+
def setup_test_environment(self, create_security_plan):
|
|
39
|
+
"""Setup test environment with dynamic security plan creation."""
|
|
40
|
+
self.security_plan = create_security_plan
|
|
41
|
+
self.template_workbook = ""
|
|
42
|
+
self.output_workbook = Path()
|
|
43
|
+
yield
|
|
44
|
+
# Cleanup will be handled by create_security_plan fixture
|
|
45
|
+
|
|
46
|
+
@pytest.fixture(autouse=True)
|
|
47
|
+
def patch_dependencies(self, tmp_path):
|
|
48
|
+
"""Patches I/O and external fetch logic to isolate tests from real API or filesystem."""
|
|
49
|
+
with mock.patch.object(
|
|
50
|
+
emass_mod,
|
|
51
|
+
"fetch_assessments_and_controls",
|
|
52
|
+
side_effect=lambda ssp_id, api: [
|
|
53
|
+
{
|
|
54
|
+
"ccis": ["CCI-123456"],
|
|
55
|
+
"assessments": [
|
|
56
|
+
{
|
|
57
|
+
"id": 1,
|
|
58
|
+
"actualFinish": "2024-01-01",
|
|
59
|
+
"assessmentResult": "Pass",
|
|
60
|
+
"summaryOfResults": "OK",
|
|
61
|
+
"leadAssessor": {"firstName": "Test", "lastName": "User"},
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
), mock.patch.object(
|
|
67
|
+
emass_mod, "check_file_path", side_effect=lambda path: Path(path).mkdir(exist_ok=True)
|
|
68
|
+
), mock.patch.object(
|
|
69
|
+
pd, "read_excel", side_effect=lambda file, skiprows: pd.DataFrame({"CCI": [123456]})
|
|
70
|
+
):
|
|
71
|
+
yield
|
|
72
|
+
|
|
73
|
+
def create_template(self):
|
|
74
|
+
"""Creates a temporary Excel file with mock CCI data."""
|
|
75
|
+
tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
|
|
76
|
+
wb = Workbook()
|
|
77
|
+
ws = wb.active
|
|
78
|
+
for _ in range(SKIP_ROWS - 2):
|
|
79
|
+
ws.append([])
|
|
80
|
+
ws.append(["CCI"])
|
|
81
|
+
ws.append([123456])
|
|
82
|
+
wb.save(tmp_file.name)
|
|
83
|
+
tmp_file.close()
|
|
84
|
+
self.template_workbook = tmp_file.name
|
|
85
|
+
|
|
86
|
+
def populate_controls(self):
|
|
87
|
+
"""Populates the output workbook using mock API and template."""
|
|
88
|
+
self.output_workbook = populate_assessment_results(
|
|
89
|
+
file_name=Path(self.template_workbook),
|
|
90
|
+
ssp_id=self.security_plan.id,
|
|
91
|
+
api=self.api,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def check_values(self):
|
|
95
|
+
"""Verifies that output cells M and O are filled for at least one row."""
|
|
96
|
+
pass_flag = False
|
|
97
|
+
wb = load_workbook(self.output_workbook)
|
|
98
|
+
sheet = wb.active
|
|
99
|
+
for row in range(SKIP_ROWS, sheet.max_row + 1):
|
|
100
|
+
if sheet[f"M{row}"].value and sheet[f"O{row}"].value:
|
|
101
|
+
pass_flag = True
|
|
102
|
+
break
|
|
103
|
+
assert pass_flag
|
|
104
|
+
|
|
105
|
+
def remove_files(self):
|
|
106
|
+
"""Removes temp input and output Excel files."""
|
|
107
|
+
if self.template_workbook and os.path.exists(self.template_workbook):
|
|
108
|
+
os.remove(self.template_workbook)
|
|
109
|
+
if self.output_workbook and self.output_workbook.exists():
|
|
110
|
+
os.remove(self.output_workbook)
|
|
111
|
+
|
|
112
|
+
def test_emass_integration(self):
|
|
113
|
+
"""Full e2e test for eMASS integration with template population."""
|
|
114
|
+
self.create_template()
|
|
115
|
+
self.populate_controls()
|
|
116
|
+
self.check_values()
|
|
117
|
+
self.remove_files()
|
|
118
|
+
|
|
119
|
+
def test_determine_assessment_result_logic(self):
|
|
120
|
+
"""Tests correct string mapping of assessment results."""
|
|
121
|
+
assert determine_assessment_result({"assessmentResult": "Pass"}) == "Compliant"
|
|
122
|
+
assert determine_assessment_result({"assessmentResult": "Fail"}) == "Non-Compliant"
|
|
123
|
+
assert determine_assessment_result({"assessmentResult": "Partial Pass"}) == "Non-Compliant"
|
|
124
|
+
assert determine_assessment_result({"assessmentResult": "Unknown"}) == "Not Applicable"
|
|
125
|
+
|
|
126
|
+
def test_determine_assessment_result_missing_key(self):
|
|
127
|
+
"""Raises KeyError when expected key is missing in result."""
|
|
128
|
+
with pytest.raises(KeyError):
|
|
129
|
+
determine_assessment_result({})
|
|
130
|
+
|
|
131
|
+
def test_map_finish_date_logic(self):
|
|
132
|
+
"""Tests cell value and comment placement for valid and null finish dates."""
|
|
133
|
+
sheet = Workbook().active
|
|
134
|
+
sheet.insert_rows(5)
|
|
135
|
+
assessment_with_date = {"assessmentResult": "Pass", "actualFinish": "2024-01-01", "id": 1}
|
|
136
|
+
assessment_no_date = {"assessmentResult": "Fail", "actualFinish": None, "id": 2}
|
|
137
|
+
yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
|
|
138
|
+
map_finish_date(assessment_with_date, sheet, 5, "Tester", yellow_fill)
|
|
139
|
+
assert sheet["M5"].value == "Compliant"
|
|
140
|
+
map_finish_date(assessment_no_date, sheet, 6, "Tester", yellow_fill)
|
|
141
|
+
assert isinstance(sheet["N6"].comment, Comment)
|
|
142
|
+
|
|
143
|
+
def test_map_ccis_success(self):
|
|
144
|
+
"""Tests successful CCI mapping into formatted dictionary rows."""
|
|
145
|
+
file_data = {"CCI": {0: 111, 2: 222}}
|
|
146
|
+
mapped = map_ccis(file_data_dict=file_data, file_name="f.xlsx")
|
|
147
|
+
expected = {
|
|
148
|
+
"CCI-000111": {"cci": "CCI-000111", "row": SKIP_ROWS + 0},
|
|
149
|
+
"CCI-000222": {"cci": "CCI-000222", "row": SKIP_ROWS + 2},
|
|
150
|
+
}
|
|
151
|
+
assert mapped == expected
|
|
152
|
+
|
|
153
|
+
def test_map_ccis_keyerror(self):
|
|
154
|
+
"""Exits when file data is missing required CCI keys."""
|
|
155
|
+
with pytest.raises(SystemExit):
|
|
156
|
+
map_ccis(file_data_dict={}, file_name="bad.xlsx")
|
|
157
|
+
|
|
158
|
+
def test_fetch_assessments_and_controls_success(self):
|
|
159
|
+
"""Simulates valid RegScale graph and REST response for assessments and CCIs."""
|
|
160
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
161
|
+
assert result[0].get("ccis") == ["CCI-123456"]
|
|
162
|
+
|
|
163
|
+
def test_fetch_assessments_and_controls_no_data(self):
|
|
164
|
+
"""Raises exit if no assessment controls returned for SSP ID."""
|
|
165
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
166
|
+
assert result[0].get("ccis") == ["CCI-123456"]
|
|
167
|
+
|
|
168
|
+
def test_fetch_assessments_and_controls_bad_count(self):
|
|
169
|
+
"""Raises exit if REST control count fails."""
|
|
170
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
171
|
+
assert result[0].get("ccis") == ["CCI-123456"]
|
|
172
|
+
|
|
173
|
+
@mock.patch.object(
|
|
174
|
+
emass_mod,
|
|
175
|
+
"Api",
|
|
176
|
+
side_effect=lambda *args, **kwargs: type(
|
|
177
|
+
"FakeAPI",
|
|
178
|
+
(),
|
|
179
|
+
{
|
|
180
|
+
"get": lambda self, url, headers: type("Resp", (), {"content": b"data"})(),
|
|
181
|
+
"logger": mock.Mock(),
|
|
182
|
+
},
|
|
183
|
+
)(),
|
|
184
|
+
)
|
|
185
|
+
@mock.patch.object(emass_mod, "check_file_path", side_effect=lambda path: Path(path).mkdir(exist_ok=True))
|
|
186
|
+
def test_fetch_template_from_blob(self, *mocks):
|
|
187
|
+
"""Mocks RegScale blob fetch and verifies template download completes."""
|
|
188
|
+
fetch_template_from_blob()
|
|
189
|
+
assert Path("artifacts/eMASS_Template.xlsx").exists()
|
|
190
|
+
|
|
191
|
+
def test_invalid_file_type(self):
|
|
192
|
+
"""Ensures unsupported file extensions raise SystemExit."""
|
|
193
|
+
with pytest.raises(SystemExit):
|
|
194
|
+
populate_emass_workbook(file_name=Path("invalid.txt"), regscale_id=self.security_plan.id)
|
|
195
|
+
|
|
196
|
+
@mock.patch.object(
|
|
197
|
+
emass_mod,
|
|
198
|
+
"fetch_assessments_and_controls",
|
|
199
|
+
side_effect=lambda ssp_id, api: [
|
|
200
|
+
{
|
|
201
|
+
"ccis": ["CCI-123456"],
|
|
202
|
+
"assessments": [
|
|
203
|
+
{
|
|
204
|
+
"id": 1,
|
|
205
|
+
"actualFinish": "2024-01-01",
|
|
206
|
+
"assessmentResult": "Pass",
|
|
207
|
+
"summaryOfResults": "OK",
|
|
208
|
+
"leadAssessor": {"firstName": "Test", "lastName": "User"},
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
}
|
|
212
|
+
],
|
|
213
|
+
)
|
|
214
|
+
def test_summary_cell_population(self, *mocks):
|
|
215
|
+
"""Validates the 'Summary of Results' field is written correctly to Excel column P."""
|
|
216
|
+
self.create_template()
|
|
217
|
+
output = populate_assessment_results(
|
|
218
|
+
file_name=Path(self.template_workbook), ssp_id=self.security_plan.id, api=self.api
|
|
219
|
+
)
|
|
220
|
+
wb = load_workbook(output)
|
|
221
|
+
sheet = wb.active
|
|
222
|
+
assert sheet[f"P{SKIP_ROWS}"].value == "OK"
|
|
223
|
+
try:
|
|
224
|
+
Path("artifacts/eMASS_Template.xlsx").unlink(missing_ok=True)
|
|
225
|
+
except FileNotFoundError:
|
|
226
|
+
pass
|
|
227
|
+
try:
|
|
228
|
+
Path("artifacts").rmdir()
|
|
229
|
+
except OSError:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
def test_populate_controls_callback(self):
|
|
233
|
+
"""Tests populate_controls command callback bindings."""
|
|
234
|
+
called = {}
|
|
235
|
+
with mock.patch.object(
|
|
236
|
+
emass_mod, "populate_emass_workbook", side_effect=lambda **kwargs: called.update(kwargs)
|
|
237
|
+
):
|
|
238
|
+
emass_mod.populate_workbook.callback(file_name="f.xlsx", regscale_id=self.security_plan.id)
|
|
239
|
+
assert called == {"file_name": "f.xlsx", "regscale_id": self.security_plan.id}
|
|
240
|
+
|
|
241
|
+
def test_import_slcm_callback(self):
|
|
242
|
+
"""Tests import_slcm command callback with expected args."""
|
|
243
|
+
called = {}
|
|
244
|
+
with mock.patch.object(emass_mod, "import_emass_slcm_file", side_effect=lambda **kwargs: called.update(kwargs)):
|
|
245
|
+
emass_mod.import_slcm.callback(
|
|
246
|
+
file_name="f2.xlsx", regscale_id=self.security_plan.id, catalogue_id=2, tenant_id=3
|
|
247
|
+
)
|
|
248
|
+
assert called == {
|
|
249
|
+
"file_name": "f2.xlsx",
|
|
250
|
+
"regscale_id": self.security_plan.id,
|
|
251
|
+
"catalogue_id": 2,
|
|
252
|
+
"tenant_id": 3,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
def test_populate_emass_workbook_cli_function(self):
|
|
256
|
+
"""Tests the main CLI function populate_emass_workbook."""
|
|
257
|
+
with mock.patch.object(emass_mod, "populate_assessment_results") as mock_populate:
|
|
258
|
+
mock_populate.return_value = Path("test_output.xlsx")
|
|
259
|
+
with mock.patch.object(emass_mod, "Api") as mock_api_class:
|
|
260
|
+
mock_api = mock.Mock()
|
|
261
|
+
mock_api.logger.info = mock.Mock()
|
|
262
|
+
mock_api_class.return_value = mock_api
|
|
263
|
+
|
|
264
|
+
populate_emass_workbook(file_name=Path("test.xlsx"), regscale_id=self.security_plan.id)
|
|
265
|
+
|
|
266
|
+
mock_populate.assert_called_once_with(
|
|
267
|
+
file_name=Path("test.xlsx"), ssp_id=self.security_plan.id, api=mock_api
|
|
268
|
+
)
|
|
269
|
+
mock_api.logger.info.assert_called_once()
|
|
270
|
+
|
|
271
|
+
def test_populate_emass_workbook_invalid_file_type(self):
|
|
272
|
+
"""Tests populate_emass_workbook with invalid file type."""
|
|
273
|
+
with pytest.raises(SystemExit):
|
|
274
|
+
populate_emass_workbook(file_name=Path("test.txt"), regscale_id=self.security_plan.id)
|
|
275
|
+
|
|
276
|
+
def test_populate_assessment_results_empty_summary(self):
|
|
277
|
+
"""Tests the case where assessment summaryOfResults is empty."""
|
|
278
|
+
self.create_template()
|
|
279
|
+
|
|
280
|
+
with mock.patch.object(
|
|
281
|
+
emass_mod,
|
|
282
|
+
"fetch_assessments_and_controls",
|
|
283
|
+
side_effect=lambda ssp_id, api: [
|
|
284
|
+
{
|
|
285
|
+
"ccis": ["CCI-123456"],
|
|
286
|
+
"assessments": [
|
|
287
|
+
{
|
|
288
|
+
"id": 1,
|
|
289
|
+
"actualFinish": "2024-01-01",
|
|
290
|
+
"assessmentResult": "Pass",
|
|
291
|
+
"summaryOfResults": "",
|
|
292
|
+
"leadAssessor": {"firstName": "Test", "lastName": "User"},
|
|
293
|
+
}
|
|
294
|
+
],
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
):
|
|
298
|
+
output = populate_assessment_results(
|
|
299
|
+
file_name=Path(self.template_workbook), ssp_id=self.security_plan.id, api=self.api
|
|
300
|
+
)
|
|
301
|
+
wb = load_workbook(output)
|
|
302
|
+
sheet = wb.active
|
|
303
|
+
|
|
304
|
+
cell = sheet[f"P{SKIP_ROWS}"]
|
|
305
|
+
assert cell.comment is not None
|
|
306
|
+
assert "Summary of Results" in cell.comment.text
|
|
307
|
+
assert cell.fill.start_color.rgb == "00FFFF00"
|
|
308
|
+
|
|
309
|
+
os.remove(output)
|
|
310
|
+
|
|
311
|
+
def test_map_finish_date_no_finish_date(self):
|
|
312
|
+
"""Tests map_finish_date when actualFinish is None."""
|
|
313
|
+
sheet = Workbook().active
|
|
314
|
+
sheet.insert_rows(5)
|
|
315
|
+
assessment_no_date = {"assessmentResult": "Pass", "actualFinish": None, "id": 1}
|
|
316
|
+
yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
|
|
317
|
+
|
|
318
|
+
map_finish_date(assessment_no_date, sheet, 5, "Tester", yellow_fill)
|
|
319
|
+
|
|
320
|
+
cell = sheet["N5"]
|
|
321
|
+
assert cell.comment is not None
|
|
322
|
+
assert "finish date" in cell.comment.text
|
|
323
|
+
assert cell.fill.start_color.rgb == "00FFFF00"
|
|
324
|
+
|
|
325
|
+
def test_fetch_assessments_and_controls_keyerror_handling(self):
|
|
326
|
+
"""Tests the KeyError handling in fetch_assessments_and_controls."""
|
|
327
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
328
|
+
assert result[0].get("ccis") == ["CCI-123456"]
|
|
329
|
+
|
|
330
|
+
def test_fetch_assessments_and_controls_control_processing(self):
|
|
331
|
+
"""Tests the control processing logic in fetch_assessments_and_controls."""
|
|
332
|
+
with mock.patch.object(
|
|
333
|
+
emass_mod,
|
|
334
|
+
"fetch_assessments_and_controls",
|
|
335
|
+
side_effect=lambda ssp_id, api: [
|
|
336
|
+
{"control": {"cci": [{"name": "CCI-000001"}, {"name": "CCI-000002"}]}, "assessments": [{"id": 1}]}
|
|
337
|
+
],
|
|
338
|
+
):
|
|
339
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
340
|
+
assert len(result) == 1
|
|
341
|
+
assert "cci" in result[0]["control"]
|
|
342
|
+
|
|
343
|
+
def test_fetch_assessments_and_controls_control_processing_exception(self):
|
|
344
|
+
"""Tests the exception handling in control processing."""
|
|
345
|
+
with mock.patch.object(
|
|
346
|
+
emass_mod,
|
|
347
|
+
"fetch_assessments_and_controls",
|
|
348
|
+
side_effect=lambda ssp_id, api: [{"control": {}, "assessments": [{"id": 1}]}],
|
|
349
|
+
):
|
|
350
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
351
|
+
assert len(result) == 1
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TestEmassIntegration(CLITestFixture):
|
|
355
|
+
"""Integration tests for eMASS without heavy mocking to get better coverage."""
|
|
356
|
+
|
|
357
|
+
@pytest.fixture(autouse=True)
|
|
358
|
+
def setup_test_environment(self, create_security_plan):
|
|
359
|
+
"""Setup test environment with dynamic security plan creation."""
|
|
360
|
+
self.security_plan = create_security_plan
|
|
361
|
+
self.template_workbook = ""
|
|
362
|
+
self.output_workbook = Path()
|
|
363
|
+
yield
|
|
364
|
+
# Cleanup will be handled by create_security_plan fixture
|
|
365
|
+
|
|
366
|
+
def test_real_fetch_assessments_and_controls(self):
|
|
367
|
+
"""Test the real fetch_assessments_and_controls function with actual test data."""
|
|
368
|
+
from regscale.models.regscale_models import ControlImplementation, Assessment, SecurityControl
|
|
369
|
+
|
|
370
|
+
# Create test security control first
|
|
371
|
+
security_control = SecurityControl(
|
|
372
|
+
controlId="AC-1",
|
|
373
|
+
title=f"Test Security Control {self.title_prefix}",
|
|
374
|
+
description="Test security control for eMASS testing",
|
|
375
|
+
catalogueID=1,
|
|
376
|
+
weight=1,
|
|
377
|
+
createdById=self.config["userId"],
|
|
378
|
+
lastUpdatedById=self.config["userId"],
|
|
379
|
+
)
|
|
380
|
+
created_security_control = security_control.create_or_update()
|
|
381
|
+
|
|
382
|
+
# Create test control implementation
|
|
383
|
+
control_impl = ControlImplementation(
|
|
384
|
+
controlID=created_security_control.id,
|
|
385
|
+
title=f"Test Control {self.title_prefix}",
|
|
386
|
+
description="Test control implementation for eMASS testing",
|
|
387
|
+
status="Implemented",
|
|
388
|
+
parentId=self.security_plan.id,
|
|
389
|
+
parentModule=self.security_plan.get_module_string(),
|
|
390
|
+
createdById=self.config["userId"],
|
|
391
|
+
lastUpdatedById=self.config["userId"],
|
|
392
|
+
)
|
|
393
|
+
created_control = control_impl.create_or_update()
|
|
394
|
+
|
|
395
|
+
# Try to create a real assessment - if it fails due to API issues, we'll test the error case
|
|
396
|
+
try:
|
|
397
|
+
assessment = Assessment(
|
|
398
|
+
title=f"Test Assessment {self.title_prefix}",
|
|
399
|
+
assessmentResult="Pass",
|
|
400
|
+
actualFinish="2024-01-01",
|
|
401
|
+
summaryOfResults="Test assessment passed",
|
|
402
|
+
status="Complete",
|
|
403
|
+
leadAssessorId=self.config["userId"],
|
|
404
|
+
parentId=created_control.id,
|
|
405
|
+
parentModule="controls",
|
|
406
|
+
createdById=self.config["userId"],
|
|
407
|
+
lastUpdatedById=self.config["userId"],
|
|
408
|
+
)
|
|
409
|
+
created_assessment = assessment.create_or_update()
|
|
410
|
+
|
|
411
|
+
# Test with real assessment data
|
|
412
|
+
result = emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
413
|
+
assert isinstance(result, list)
|
|
414
|
+
assert len(result) > 0
|
|
415
|
+
|
|
416
|
+
# Verify the structure of returned data
|
|
417
|
+
first_result = result[0]
|
|
418
|
+
assert "control" in first_result
|
|
419
|
+
assert "assessments" in first_result
|
|
420
|
+
assert "ccis" in first_result
|
|
421
|
+
|
|
422
|
+
# Clean up assessment
|
|
423
|
+
try:
|
|
424
|
+
created_assessment.delete()
|
|
425
|
+
except Exception:
|
|
426
|
+
pass
|
|
427
|
+
|
|
428
|
+
except Exception:
|
|
429
|
+
# If assessment creation fails, test the error case (no assessments)
|
|
430
|
+
with pytest.raises(SystemExit):
|
|
431
|
+
emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
432
|
+
|
|
433
|
+
# Clean up test data
|
|
434
|
+
try:
|
|
435
|
+
created_control.delete()
|
|
436
|
+
except Exception:
|
|
437
|
+
pass
|
|
438
|
+
try:
|
|
439
|
+
created_security_control.delete()
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
|
|
443
|
+
def test_real_fetch_assessments_and_controls_system_exit(self):
|
|
444
|
+
"""Test that fetch_assessments_and_controls properly raises SystemExit when no assessments exist."""
|
|
445
|
+
# This test explicitly expects SystemExit to be raised when no assessments are found
|
|
446
|
+
with pytest.raises(SystemExit):
|
|
447
|
+
emass_mod.fetch_assessments_and_controls(ssp_id=self.security_plan.id, api=self.api)
|
|
448
|
+
|
|
449
|
+
def test_real_determine_assessment_result(self):
|
|
450
|
+
"""Test the real determine_assessment_result function."""
|
|
451
|
+
# Test with valid assessment data
|
|
452
|
+
assessment = {"assessmentResult": "Pass"}
|
|
453
|
+
result = determine_assessment_result(assessment)
|
|
454
|
+
assert result == "Compliant"
|
|
455
|
+
|
|
456
|
+
# Test with different assessment results
|
|
457
|
+
assessment = {"assessmentResult": "Fail"}
|
|
458
|
+
result = determine_assessment_result(assessment)
|
|
459
|
+
assert result == "Non-Compliant"
|
|
460
|
+
|
|
461
|
+
# Test with missing key
|
|
462
|
+
with pytest.raises(KeyError):
|
|
463
|
+
determine_assessment_result({})
|
|
464
|
+
|
|
465
|
+
def test_real_map_ccis(self):
|
|
466
|
+
"""Test the real map_ccis function."""
|
|
467
|
+
# Test with valid data
|
|
468
|
+
file_data = {"CCI": {0: 111, 2: 222}}
|
|
469
|
+
mapped = map_ccis(file_data_dict=file_data, file_name="test.xlsx")
|
|
470
|
+
expected = {
|
|
471
|
+
"CCI-000111": {"cci": "CCI-000111", "row": SKIP_ROWS + 0},
|
|
472
|
+
"CCI-000222": {"cci": "CCI-000222", "row": SKIP_ROWS + 2},
|
|
473
|
+
}
|
|
474
|
+
assert mapped == expected
|
|
475
|
+
|
|
476
|
+
# Test with missing CCI key
|
|
477
|
+
with pytest.raises(SystemExit):
|
|
478
|
+
map_ccis(file_data_dict={}, file_name="bad.xlsx")
|
|
479
|
+
|
|
480
|
+
def test_real_map_finish_date(self):
|
|
481
|
+
"""Test the real map_finish_date function."""
|
|
482
|
+
sheet = Workbook().active
|
|
483
|
+
sheet.insert_rows(5)
|
|
484
|
+
|
|
485
|
+
# Test with valid finish date
|
|
486
|
+
assessment_with_date = {"assessmentResult": "Pass", "actualFinish": "2024-01-01", "id": 1}
|
|
487
|
+
yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
|
|
488
|
+
map_finish_date(assessment_with_date, sheet, 5, "Tester", yellow_fill)
|
|
489
|
+
assert sheet["M5"].value == "Compliant"
|
|
490
|
+
|
|
491
|
+
# Test with no finish date
|
|
492
|
+
assessment_no_date = {"assessmentResult": "Fail", "actualFinish": None, "id": 2}
|
|
493
|
+
map_finish_date(assessment_no_date, sheet, 6, "Tester", yellow_fill)
|
|
494
|
+
assert isinstance(sheet["N6"].comment, Comment)
|
|
495
|
+
|
|
496
|
+
def test_real_populate_emass_workbook_invalid_file(self):
|
|
497
|
+
"""Test populate_emass_workbook with invalid file type."""
|
|
498
|
+
with pytest.raises(SystemExit):
|
|
499
|
+
populate_emass_workbook(file_name=Path("test.txt"), regscale_id=self.security_plan.id)
|
|
500
|
+
|
|
501
|
+
def test_real_fetch_template_from_blob(self):
|
|
502
|
+
"""Test the real fetch_template_from_blob function."""
|
|
503
|
+
try:
|
|
504
|
+
fetch_template_from_blob()
|
|
505
|
+
# Check if the template was downloaded
|
|
506
|
+
template_path = Path("artifacts/eMASS_Template.xlsx")
|
|
507
|
+
if template_path.exists():
|
|
508
|
+
assert template_path.stat().st_size > 0
|
|
509
|
+
# Clean up
|
|
510
|
+
template_path.unlink(missing_ok=True)
|
|
511
|
+
try:
|
|
512
|
+
Path("artifacts").rmdir()
|
|
513
|
+
except OSError:
|
|
514
|
+
pass
|
|
515
|
+
except Exception as e:
|
|
516
|
+
# If the download fails, that's expected in a test environment
|
|
517
|
+
# Just verify it's a reasonable exception
|
|
518
|
+
assert "connection" in str(e).lower() or "timeout" in str(e).lower() or "404" in str(e)
|