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
tests/core/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for the async GraphQL client."""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from unittest import TestCase
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|
8
|
+
|
|
9
|
+
import anyio
|
|
10
|
+
import pytest
|
|
11
|
+
from gql import Client, gql
|
|
12
|
+
from gql.transport.aiohttp import AIOHTTPTransport
|
|
13
|
+
from gql.transport.exceptions import TransportQueryError
|
|
14
|
+
|
|
15
|
+
from regscale.core.utils.async_graphql_client import (
|
|
16
|
+
AsyncRegScaleGraphQLClient,
|
|
17
|
+
run_async_paginated_query,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestAsyncRegScaleGraphQLClient(TestCase):
|
|
22
|
+
"""Test cases for AsyncRegScaleGraphQLClient."""
|
|
23
|
+
|
|
24
|
+
def setUp(self):
|
|
25
|
+
"""Set up test fixtures."""
|
|
26
|
+
self.endpoint = "https://test.regscale.com/graphql"
|
|
27
|
+
self.headers = {"Authorization": "Bearer test_token"}
|
|
28
|
+
self.client = AsyncRegScaleGraphQLClient(endpoint=self.endpoint, headers=self.headers, max_concurrent=3)
|
|
29
|
+
|
|
30
|
+
def test_init(self):
|
|
31
|
+
"""Test client initialization."""
|
|
32
|
+
client = AsyncRegScaleGraphQLClient(
|
|
33
|
+
endpoint=self.endpoint,
|
|
34
|
+
headers=self.headers,
|
|
35
|
+
timeout=45.0,
|
|
36
|
+
max_concurrent=10,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self.assertEqual(client.endpoint, self.endpoint)
|
|
40
|
+
self.assertEqual(client.headers, self.headers)
|
|
41
|
+
self.assertEqual(client.timeout, 45.0)
|
|
42
|
+
self.assertEqual(client.max_concurrent, 10)
|
|
43
|
+
|
|
44
|
+
# Verify _create_client method creates a new client with correct params
|
|
45
|
+
with patch("regscale.core.utils.async_graphql_client.AIOHTTPTransport") as mock_transport_class:
|
|
46
|
+
with patch("regscale.core.utils.async_graphql_client.Client") as mock_client_class:
|
|
47
|
+
client._create_client()
|
|
48
|
+
|
|
49
|
+
# Verify transport was created with correct params
|
|
50
|
+
mock_transport_class.assert_called_once()
|
|
51
|
+
call_args = mock_transport_class.call_args
|
|
52
|
+
self.assertEqual(call_args[1]["url"], self.endpoint)
|
|
53
|
+
self.assertEqual(call_args[1]["headers"], self.headers)
|
|
54
|
+
self.assertEqual(call_args[1]["timeout"], 45) # Converted to int
|
|
55
|
+
|
|
56
|
+
# Verify client was created
|
|
57
|
+
mock_client_class.assert_called_once()
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_execute_query_success(self):
|
|
61
|
+
"""Test successful query execution."""
|
|
62
|
+
with patch.object(self.client.client, "__aenter__") as mock_aenter:
|
|
63
|
+
mock_session = AsyncMock()
|
|
64
|
+
mock_session.execute = AsyncMock(
|
|
65
|
+
return_value={
|
|
66
|
+
"issues": {
|
|
67
|
+
"items": [{"id": 1, "title": "Test Issue"}],
|
|
68
|
+
"totalCount": 1,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
mock_aenter.return_value = mock_session
|
|
73
|
+
|
|
74
|
+
query = """
|
|
75
|
+
query {
|
|
76
|
+
issues(skip: 0, take: 10) {
|
|
77
|
+
items { id title }
|
|
78
|
+
totalCount
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
result = await self.client.execute_query(query)
|
|
84
|
+
|
|
85
|
+
self.assertIn("issues", result)
|
|
86
|
+
self.assertEqual(result["issues"]["totalCount"], 1)
|
|
87
|
+
mock_session.execute.assert_called_once()
|
|
88
|
+
|
|
89
|
+
@pytest.mark.asyncio
|
|
90
|
+
async def test_execute_query_with_variables(self):
|
|
91
|
+
"""Test query execution with variables."""
|
|
92
|
+
with patch.object(self.client.client, "__aenter__") as mock_aenter:
|
|
93
|
+
mock_session = AsyncMock()
|
|
94
|
+
mock_session.execute = AsyncMock(return_value={"data": {"result": "success"}})
|
|
95
|
+
mock_aenter.return_value = mock_session
|
|
96
|
+
|
|
97
|
+
query = "query($id: Int!) { issue(id: $id) { title } }"
|
|
98
|
+
variables = {"id": 123}
|
|
99
|
+
|
|
100
|
+
await self.client.execute_query(query, variables=variables)
|
|
101
|
+
|
|
102
|
+
mock_session.execute.assert_called_once()
|
|
103
|
+
call_args = mock_session.execute.call_args
|
|
104
|
+
self.assertEqual(call_args[1]["variable_values"], variables)
|
|
105
|
+
|
|
106
|
+
@pytest.mark.asyncio
|
|
107
|
+
async def test_execute_query_with_auth_error_and_retry(self):
|
|
108
|
+
"""Test query execution with auth error and successful retry."""
|
|
109
|
+
token_refresh_callback = Mock(return_value="new_token")
|
|
110
|
+
client = AsyncRegScaleGraphQLClient(
|
|
111
|
+
endpoint=self.endpoint,
|
|
112
|
+
headers=self.headers,
|
|
113
|
+
token_refresh_callback=token_refresh_callback,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
with patch.object(client.client, "__aenter__") as mock_aenter:
|
|
117
|
+
mock_session = AsyncMock()
|
|
118
|
+
# First call fails with auth error, second succeeds
|
|
119
|
+
mock_session.execute = AsyncMock(
|
|
120
|
+
side_effect=[
|
|
121
|
+
TransportQueryError("AUTH_NOT_AUTHENTICATED"),
|
|
122
|
+
{"data": {"result": "success"}},
|
|
123
|
+
]
|
|
124
|
+
)
|
|
125
|
+
mock_aenter.return_value = mock_session
|
|
126
|
+
|
|
127
|
+
# Need to patch transport recreation
|
|
128
|
+
with patch("regscale.core.utils.async_graphql_client.AIOHTTPTransport"):
|
|
129
|
+
with patch("regscale.core.utils.async_graphql_client.Client") as mock_client:
|
|
130
|
+
# Set up the new client
|
|
131
|
+
new_client = MagicMock()
|
|
132
|
+
new_session = AsyncMock()
|
|
133
|
+
new_session.execute = AsyncMock(return_value={"data": {"result": "success"}})
|
|
134
|
+
new_client.__aenter__ = AsyncMock(return_value=new_session)
|
|
135
|
+
mock_client.return_value = new_client
|
|
136
|
+
|
|
137
|
+
query = "query { test }"
|
|
138
|
+
result = await client.execute_query(query)
|
|
139
|
+
|
|
140
|
+
self.assertEqual(result, {"data": {"result": "success"}})
|
|
141
|
+
token_refresh_callback.assert_called_once()
|
|
142
|
+
|
|
143
|
+
@pytest.mark.asyncio
|
|
144
|
+
async def test_execute_query_with_non_auth_error(self):
|
|
145
|
+
"""Test query execution with non-auth error."""
|
|
146
|
+
with patch.object(self.client.client, "__aenter__") as mock_aenter:
|
|
147
|
+
mock_session = AsyncMock()
|
|
148
|
+
mock_session.execute = AsyncMock(side_effect=Exception("Network error"))
|
|
149
|
+
mock_aenter.return_value = mock_session
|
|
150
|
+
|
|
151
|
+
query = "query { test }"
|
|
152
|
+
|
|
153
|
+
with self.assertRaises(Exception) as context:
|
|
154
|
+
await self.client.execute_query(query)
|
|
155
|
+
|
|
156
|
+
self.assertIn("Network error", str(context.exception))
|
|
157
|
+
|
|
158
|
+
@pytest.mark.asyncio
|
|
159
|
+
async def test_execute_query_with_progress_callback(self):
|
|
160
|
+
"""Test query execution with progress callback."""
|
|
161
|
+
progress_callback = Mock()
|
|
162
|
+
|
|
163
|
+
with patch.object(self.client.client, "__aenter__") as mock_aenter:
|
|
164
|
+
mock_session = AsyncMock()
|
|
165
|
+
mock_session.execute = AsyncMock(return_value={"data": {"test": "data"}})
|
|
166
|
+
mock_aenter.return_value = mock_session
|
|
167
|
+
|
|
168
|
+
query = "query { test }"
|
|
169
|
+
await self.client.execute_query(query, progress_callback=progress_callback, task_name="Test Query")
|
|
170
|
+
|
|
171
|
+
# Verify progress callbacks were made
|
|
172
|
+
progress_callback.assert_any_call("Test Query", "starting")
|
|
173
|
+
progress_callback.assert_any_call("Test Query", "requesting")
|
|
174
|
+
progress_callback.assert_any_call("Test Query", "completed")
|
|
175
|
+
|
|
176
|
+
@pytest.mark.asyncio
|
|
177
|
+
async def test_execute_paginated_query_concurrent(self):
|
|
178
|
+
"""Test concurrent paginated query execution."""
|
|
179
|
+
query_builder = Mock(side_effect=lambda skip, take: f"query({skip},{take})")
|
|
180
|
+
|
|
181
|
+
with patch.object(self.client, "execute_query", new_callable=AsyncMock) as mock_execute:
|
|
182
|
+
# Simulate 3 pages of results
|
|
183
|
+
mock_execute.side_effect = [
|
|
184
|
+
{"issues": {"items": [{"id": 1}, {"id": 2}]}},
|
|
185
|
+
{"issues": {"items": [{"id": 3}, {"id": 4}]}},
|
|
186
|
+
{"issues": {"items": [{"id": 5}]}},
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
result = await self.client.execute_paginated_query_concurrent(
|
|
190
|
+
query_builder=query_builder,
|
|
191
|
+
topic_key="issues",
|
|
192
|
+
total_count=5,
|
|
193
|
+
page_size=2,
|
|
194
|
+
starting_skip=0,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self.assertEqual(len(result), 5)
|
|
198
|
+
self.assertEqual(query_builder.call_count, 3)
|
|
199
|
+
self.assertEqual(mock_execute.call_count, 3)
|
|
200
|
+
|
|
201
|
+
@pytest.mark.asyncio
|
|
202
|
+
async def test_fetch_single_page_success(self):
|
|
203
|
+
"""Test successful single page fetch."""
|
|
204
|
+
with patch.object(self.client, "execute_query", new_callable=AsyncMock) as mock_execute:
|
|
205
|
+
mock_execute.return_value = {"issues": {"items": [{"id": 1}, {"id": 2}], "totalCount": 2}}
|
|
206
|
+
|
|
207
|
+
result = await self.client._fetch_single_page(
|
|
208
|
+
query="test query",
|
|
209
|
+
variables={},
|
|
210
|
+
topic_key="issues",
|
|
211
|
+
page_num=1,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
self.assertEqual(len(result), 2)
|
|
215
|
+
self.assertEqual(result[0]["id"], 1)
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
async def test_fetch_single_page_with_none_items(self):
|
|
219
|
+
"""Test single page fetch when items is None."""
|
|
220
|
+
with patch.object(self.client, "execute_query", new_callable=AsyncMock) as mock_execute:
|
|
221
|
+
mock_execute.return_value = {"issues": {"items": None, "totalCount": 0}}
|
|
222
|
+
|
|
223
|
+
result = await self.client._fetch_single_page(
|
|
224
|
+
query="test query",
|
|
225
|
+
variables={},
|
|
226
|
+
topic_key="issues",
|
|
227
|
+
page_num=1,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
self.assertEqual(result, [])
|
|
231
|
+
|
|
232
|
+
@pytest.mark.asyncio
|
|
233
|
+
async def test_fetch_single_page_with_error(self):
|
|
234
|
+
"""Test single page fetch with error."""
|
|
235
|
+
with patch.object(self.client, "execute_query", new_callable=AsyncMock) as mock_execute:
|
|
236
|
+
mock_execute.side_effect = Exception("Query failed")
|
|
237
|
+
|
|
238
|
+
with self.assertRaises(Exception) as context:
|
|
239
|
+
await self.client._fetch_single_page(
|
|
240
|
+
query="test query",
|
|
241
|
+
variables={},
|
|
242
|
+
topic_key="issues",
|
|
243
|
+
page_num=1,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
self.assertIn("Query failed", str(context.exception))
|
|
247
|
+
|
|
248
|
+
@pytest.mark.asyncio
|
|
249
|
+
async def test_concurrent_pagination_with_mixed_results(self):
|
|
250
|
+
"""Test concurrent pagination with some failures."""
|
|
251
|
+
query_builder = Mock(side_effect=lambda skip, take: f"query({skip},{take})")
|
|
252
|
+
|
|
253
|
+
with patch.object(self.client, "execute_query", new_callable=AsyncMock) as mock_execute:
|
|
254
|
+
# Mix of success and failure
|
|
255
|
+
mock_execute.side_effect = [
|
|
256
|
+
{"issues": {"items": [{"id": 1}, {"id": 2}]}},
|
|
257
|
+
Exception("Page 2 failed"),
|
|
258
|
+
{"issues": {"items": [{"id": 3}]}},
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
result = await self.client.execute_paginated_query_concurrent(
|
|
262
|
+
query_builder=query_builder,
|
|
263
|
+
topic_key="issues",
|
|
264
|
+
total_count=5,
|
|
265
|
+
page_size=2,
|
|
266
|
+
starting_skip=0,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Should still get results from successful pages
|
|
270
|
+
self.assertEqual(len(result), 3) # Only pages 1 and 3 succeeded
|
|
271
|
+
|
|
272
|
+
def test_run_async_paginated_query(self):
|
|
273
|
+
"""Test the synchronous wrapper function."""
|
|
274
|
+
query_builder = Mock(side_effect=lambda skip, take: f"query({skip},{take})")
|
|
275
|
+
|
|
276
|
+
with patch("regscale.core.utils.async_graphql_client.AsyncRegScaleGraphQLClient") as mock_client_class:
|
|
277
|
+
mock_client = Mock()
|
|
278
|
+
mock_client.execute_paginated_query_concurrent = AsyncMock(return_value=[{"id": 1}, {"id": 2}])
|
|
279
|
+
mock_client_class.return_value = mock_client
|
|
280
|
+
|
|
281
|
+
result = run_async_paginated_query(
|
|
282
|
+
endpoint=self.endpoint,
|
|
283
|
+
headers=self.headers,
|
|
284
|
+
query_builder=query_builder,
|
|
285
|
+
topic_key="issues",
|
|
286
|
+
total_count=2,
|
|
287
|
+
page_size=50,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
self.assertEqual(len(result), 2)
|
|
291
|
+
mock_client_class.assert_called_once()
|
|
292
|
+
mock_client.execute_paginated_query_concurrent.assert_called_once()
|
|
293
|
+
|
|
294
|
+
def test_run_async_paginated_query_with_token_refresh(self):
|
|
295
|
+
"""Test synchronous wrapper with token refresh callback."""
|
|
296
|
+
query_builder = Mock(side_effect=lambda skip, take: f"query({skip},{take})")
|
|
297
|
+
token_refresh = Mock(return_value="new_token")
|
|
298
|
+
|
|
299
|
+
with patch("regscale.core.utils.async_graphql_client.AsyncRegScaleGraphQLClient") as mock_client_class:
|
|
300
|
+
mock_client = Mock()
|
|
301
|
+
mock_client.execute_paginated_query_concurrent = AsyncMock(return_value=[{"id": 1}])
|
|
302
|
+
mock_client_class.return_value = mock_client
|
|
303
|
+
|
|
304
|
+
run_async_paginated_query(
|
|
305
|
+
endpoint=self.endpoint,
|
|
306
|
+
headers=self.headers,
|
|
307
|
+
query_builder=query_builder,
|
|
308
|
+
topic_key="issues",
|
|
309
|
+
total_count=1,
|
|
310
|
+
token_refresh_callback=token_refresh,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Verify token refresh callback was passed to client
|
|
314
|
+
call_kwargs = mock_client_class.call_args[1]
|
|
315
|
+
self.assertEqual(call_kwargs["token_refresh_callback"], token_refresh)
|
|
316
|
+
|
|
317
|
+
@pytest.mark.asyncio
|
|
318
|
+
async def test_semaphore_limiting(self):
|
|
319
|
+
"""Test that concurrent requests are limited by semaphore."""
|
|
320
|
+
client = AsyncRegScaleGraphQLClient(endpoint=self.endpoint, headers=self.headers, max_concurrent=2)
|
|
321
|
+
|
|
322
|
+
# Track concurrent executions
|
|
323
|
+
concurrent_count = 0
|
|
324
|
+
max_concurrent_seen = 0
|
|
325
|
+
|
|
326
|
+
async def slow_execute(*args, **kwargs):
|
|
327
|
+
nonlocal concurrent_count, max_concurrent_seen
|
|
328
|
+
concurrent_count += 1
|
|
329
|
+
max_concurrent_seen = max(max_concurrent_seen, concurrent_count)
|
|
330
|
+
await asyncio.sleep(0.1)
|
|
331
|
+
concurrent_count -= 1
|
|
332
|
+
return {"data": {"items": []}}
|
|
333
|
+
|
|
334
|
+
with patch.object(client, "execute_query", side_effect=slow_execute):
|
|
335
|
+
query_builder = Mock(side_effect=lambda skip, take: f"query({skip},{take})")
|
|
336
|
+
|
|
337
|
+
# Request 5 pages with max_concurrent=2
|
|
338
|
+
await client.execute_paginated_query_concurrent(
|
|
339
|
+
query_builder=query_builder,
|
|
340
|
+
topic_key="data",
|
|
341
|
+
total_count=10,
|
|
342
|
+
page_size=2,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Should never exceed max_concurrent
|
|
346
|
+
self.assertLessEqual(max_concurrent_seen, 2)
|
|
347
|
+
|
|
348
|
+
@pytest.mark.asyncio
|
|
349
|
+
async def test_execute_query_with_timeout(self):
|
|
350
|
+
"""Test query execution respects timeout."""
|
|
351
|
+
client = AsyncRegScaleGraphQLClient(endpoint=self.endpoint, headers=self.headers, timeout=0.001)
|
|
352
|
+
|
|
353
|
+
with patch.object(client.client, "__aenter__") as mock_aenter:
|
|
354
|
+
mock_session = AsyncMock()
|
|
355
|
+
|
|
356
|
+
async def slow_execute(*args, **kwargs):
|
|
357
|
+
await asyncio.sleep(1) # Longer than timeout
|
|
358
|
+
return {"data": {}}
|
|
359
|
+
|
|
360
|
+
mock_session.execute = slow_execute
|
|
361
|
+
mock_aenter.return_value = mock_session
|
|
362
|
+
|
|
363
|
+
# Should timeout
|
|
364
|
+
with self.assertRaises(Exception):
|
|
365
|
+
await client.execute_query("query { test }")
|
|
366
|
+
|
|
367
|
+
@pytest.mark.asyncio
|
|
368
|
+
async def test_ssl_verify_setting(self):
|
|
369
|
+
"""Test SSL verification setting from ScannerVariables."""
|
|
370
|
+
with patch("regscale.core.utils.async_graphql_client.ScannerVariables") as mock_vars:
|
|
371
|
+
mock_vars.sslVerify = False
|
|
372
|
+
|
|
373
|
+
with patch("regscale.core.utils.async_graphql_client.AIOHTTPTransport") as mock_transport:
|
|
374
|
+
AsyncRegScaleGraphQLClient(endpoint=self.endpoint, headers=self.headers)
|
|
375
|
+
|
|
376
|
+
# Verify transport was created with ssl parameter
|
|
377
|
+
mock_transport.assert_called()
|
|
378
|
+
call_args = mock_transport.call_args
|
|
379
|
+
# Check that ssl parameter was set (should be ssl context when verify=False)
|
|
380
|
+
self.assertIsNotNone(call_args[1].get("ssl"))
|
|
381
|
+
|
|
382
|
+
@pytest.mark.asyncio
|
|
383
|
+
async def test_auth_error_without_callback(self):
|
|
384
|
+
"""Test auth error handling without token refresh callback."""
|
|
385
|
+
client = AsyncRegScaleGraphQLClient(
|
|
386
|
+
endpoint=self.endpoint,
|
|
387
|
+
headers=self.headers,
|
|
388
|
+
token_refresh_callback=None, # No callback
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
with patch.object(client.client, "__aenter__") as mock_aenter:
|
|
392
|
+
mock_session = AsyncMock()
|
|
393
|
+
mock_session.execute = AsyncMock(side_effect=TransportQueryError("AUTH_NOT_AUTHENTICATED"))
|
|
394
|
+
mock_aenter.return_value = mock_session
|
|
395
|
+
|
|
396
|
+
with self.assertRaises(Exception) as context:
|
|
397
|
+
await client.execute_query("query { test }")
|
|
398
|
+
|
|
399
|
+
self.assertIn("AUTH_NOT_AUTHENTICATED", str(context.exception))
|
|
400
|
+
|
|
401
|
+
@pytest.mark.asyncio
|
|
402
|
+
async def test_multiple_auth_retries_exhausted(self):
|
|
403
|
+
"""Test that auth retries are exhausted after max attempts."""
|
|
404
|
+
token_refresh = Mock(return_value="new_token")
|
|
405
|
+
client = AsyncRegScaleGraphQLClient(
|
|
406
|
+
endpoint=self.endpoint,
|
|
407
|
+
headers=self.headers,
|
|
408
|
+
token_refresh_callback=token_refresh,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
with patch.object(client.client, "__aenter__") as mock_aenter:
|
|
412
|
+
mock_session = AsyncMock()
|
|
413
|
+
# Always fail with auth error
|
|
414
|
+
mock_session.execute = AsyncMock(side_effect=TransportQueryError("AUTH_NOT_AUTHENTICATED"))
|
|
415
|
+
mock_aenter.return_value = mock_session
|
|
416
|
+
|
|
417
|
+
with patch("regscale.core.utils.async_graphql_client.AIOHTTPTransport"):
|
|
418
|
+
with patch("regscale.core.utils.async_graphql_client.Client"):
|
|
419
|
+
with self.assertRaises(Exception) as context:
|
|
420
|
+
await client.execute_query("query { test }")
|
|
421
|
+
|
|
422
|
+
# Should have tried to refresh token once
|
|
423
|
+
token_refresh.assert_called_once()
|
|
424
|
+
self.assertIn("Failed to execute query", str(context.exception))
|
|
425
|
+
|
|
426
|
+
@pytest.mark.asyncio
|
|
427
|
+
async def test_progress_callback_on_error(self):
|
|
428
|
+
"""Test progress callback is called on error."""
|
|
429
|
+
progress_callback = Mock()
|
|
430
|
+
|
|
431
|
+
with patch.object(self.client.client, "__aenter__") as mock_aenter:
|
|
432
|
+
mock_session = AsyncMock()
|
|
433
|
+
mock_session.execute = AsyncMock(side_effect=Exception("Query error"))
|
|
434
|
+
mock_aenter.return_value = mock_session
|
|
435
|
+
|
|
436
|
+
with self.assertRaises(Exception):
|
|
437
|
+
await self.client.execute_query(
|
|
438
|
+
"query { test }",
|
|
439
|
+
progress_callback=progress_callback,
|
|
440
|
+
task_name="Error Query",
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# Should have called failed status
|
|
444
|
+
progress_callback.assert_any_call("Error Query", "starting")
|
|
445
|
+
progress_callback.assert_any_call("Error Query", "requesting")
|
|
446
|
+
progress_callback.assert_any_call("Error Query", "failed")
|
|
447
|
+
|
|
448
|
+
@pytest.mark.asyncio
|
|
449
|
+
async def test_concurrent_queries_with_progress(self):
|
|
450
|
+
"""Test concurrent queries with progress tracking."""
|
|
451
|
+
progress_callback = Mock()
|
|
452
|
+
query_builder = Mock(side_effect=lambda skip, take: f"query({skip},{take})")
|
|
453
|
+
|
|
454
|
+
with patch.object(self.client, "execute_query", new_callable=AsyncMock) as mock_execute:
|
|
455
|
+
mock_execute.side_effect = [
|
|
456
|
+
{"data": {"items": [{"id": 1}]}},
|
|
457
|
+
{"data": {"items": [{"id": 2}]}},
|
|
458
|
+
]
|
|
459
|
+
|
|
460
|
+
result = await self.client.execute_paginated_query_concurrent(
|
|
461
|
+
query_builder=query_builder,
|
|
462
|
+
topic_key="data",
|
|
463
|
+
total_count=2,
|
|
464
|
+
page_size=1,
|
|
465
|
+
progress_callback=progress_callback,
|
|
466
|
+
task_name="Test",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
self.assertEqual(len(result), 2)
|
|
470
|
+
# Verify progress was tracked for each page
|
|
471
|
+
progress_callback.assert_any_call("Test (Page 1/2)", "fetched_1_items")
|
|
472
|
+
progress_callback.assert_any_call("Test (Page 2/2)", "fetched_1_items")
|
tests/fixtures/test_fixture.py
CHANGED
|
@@ -141,14 +141,19 @@ class CLITestFixture:
|
|
|
141
141
|
self.config = self.app.config
|
|
142
142
|
self.title_prefix = generate_uuid
|
|
143
143
|
# login with token if available
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
try:
|
|
145
|
+
if token := os.getenv("REGSCALE_TOKEN") or self.config.get("token"):
|
|
146
|
+
login(token=token, app=self.app)
|
|
147
|
+
elif "REGSCALE_USERNAME" in os.environ and "REGSCALE_PASSWORD" in os.environ:
|
|
148
|
+
login(
|
|
149
|
+
str_user=os.getenv("REGSCALE_USERNAME"),
|
|
150
|
+
str_password=os.getenv("REGSCALE_PASSWORD"),
|
|
151
|
+
app=self.app,
|
|
152
|
+
)
|
|
153
|
+
except SystemExit:
|
|
154
|
+
# In test environment, don't exit on authentication failure
|
|
155
|
+
logger.warning("Authentication failed in test environment - continuing without authentication")
|
|
156
|
+
pass
|
|
152
157
|
self.api = Api()
|
|
153
158
|
self.logger = logger
|
|
154
159
|
|