@ngocsangairvds/vsaf 3.1.27 → 3.2.1
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.
- package/package.json +2 -2
- package/src/global.js +70 -10
- package/tools/skills/vds-scripts-skill/.openskills.json +6 -0
- package/tools/skills/vds-scripts-skill/QUALITY.md +44 -0
- package/tools/skills/vds-scripts-skill/SKILL.md +135 -0
- package/tools/skills/vds-scripts-skill/references/audit-commands.md +171 -0
- package/tools/skills/vds-scripts-skill/references/capability-index.md +34 -0
- package/tools/skills/vds-scripts-skill/references/development-commands.md +12 -0
- package/tools/skills/vds-scripts-skill/references/google-sheets.md +73 -0
- package/tools/skills/vds-scripts-skill/references/integration-commands.md +17 -0
- package/tools/skills/vds-scripts-skill/references/platform-bootstrap.md +31 -0
- package/tools/skills/vds-scripts-skill/references/specialist-routing.md +14 -0
- package/tools/skills/vds-scripts-skill/references/validation-commands.md +15 -0
- package/tools/skills/vsaf-build/SKILL.md +32 -2
- package/tools/skills/vsaf-ship/SKILL.md +41 -10
- package/tools/skills/vsaf-test/SKILL.md +8 -0
- package/tools/vds-scripts/.mcp.json +11 -0
- package/tools/vds-scripts/.secrets.baseline +133 -0
- package/tools/vds-scripts/AGENTS.md +152 -0
- package/tools/vds-scripts/CLAUDE.md +101 -0
- package/tools/vds-scripts/CLI_COMMAND_OPTIMIZATION.md +156 -0
- package/tools/vds-scripts/PACKAGE_P125B_IMPLEMENTATION_SUMMARY.md +131 -0
- package/tools/vds-scripts/PROJECT_COMPLETION_SUMMARY.md +45 -0
- package/tools/vds-scripts/README.md +97 -0
- package/tools/vds-scripts/bitbucket_manifest_mapping.toml +34 -0
- package/tools/vds-scripts/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
- package/tools/vds-scripts/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
- package/tools/vds-scripts/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
- package/tools/vds-scripts/bitbucket_orchestrator/FEATURES.md +908 -0
- package/tools/vds-scripts/bitbucket_orchestrator/README.md +687 -0
- package/tools/vds-scripts/bitbucket_orchestrator/pyproject.toml +40 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +20 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +657 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2108 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2534 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +171 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +67 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
- package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/__init__.py +8 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/conftest.py +65 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_advanced_search.py +151 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_async_client.py +546 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_branch_permissions.py +145 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_cli.py +115 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client.py +157 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_branch_conditions.py +79 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_advanced.py +163 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_file.py +32 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_deployment_environments.py +194 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_issues.py +164 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +179 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pr_blockers.py +119 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_repository_variables.py +156 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code.py +98 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_advanced.py +282 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_insights.py +335 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_conditions.py +147 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_config.py +131 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_factory.py +371 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_fork_operations.py +204 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_issue_cli.py +261 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pipeline_advanced.py +270 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pr_blocker.py +204 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_protocols.py +334 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_settings.py +343 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_variables.py +270 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_webhooks.py +189 -0
- package/tools/vds-scripts/bitbucket_orchestrator/tests/test_workspace.py +233 -0
- package/tools/vds-scripts/bitbucket_orchestrator/uv.lock +742 -0
- package/tools/vds-scripts/confluence_orchestrator/Dockerfile +19 -0
- package/tools/vds-scripts/confluence_orchestrator/README.md +412 -0
- package/tools/vds-scripts/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
- package/tools/vds-scripts/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
- package/tools/vds-scripts/confluence_orchestrator/pyproject.toml +48 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/__init__.py +20 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/cli.py +2532 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/config.py +175 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content.py +290 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +94 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1835 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/errors.py +80 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/eventing.py +109 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/http.py +1114 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +165 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
- package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree.py +121 -0
- package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_from_markdown.py +213 -0
- package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_to_confluence.py +305 -0
- package/tools/vds-scripts/confluence_orchestrator/sync_png_attachments.py +305 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/conftest.py +8 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_content.py +224 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_search.py +188 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_cache_management.py +247 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_cli.py +499 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_config.py +83 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_content.py +186 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_content_flags.py +27 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_crawl_tree.py +2250 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_draft_management.py +223 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing.py +71 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_export.py +230 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_history.py +204 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_http.py +117 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_orchestration.py +91 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_reporting.py +24 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_search_cql.py +34 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_space_management.py +237 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_space_permissions.py +332 -0
- package/tools/vds-scripts/confluence_orchestrator/tests/test_user_group_management.py +388 -0
- package/tools/vds-scripts/confluence_orchestrator/uv.lock +1023 -0
- package/tools/vds-scripts/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
- package/tools/vds-scripts/git_orchestrator/README.md +280 -0
- package/tools/vds-scripts/git_orchestrator/VERIFICATION_REPORT.md +152 -0
- package/tools/vds-scripts/git_orchestrator/pyproject.toml +35 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__init__.py +7 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/cli.py +847 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/manifest.py +129 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +819 -0
- package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/reporting.py +53 -0
- package/tools/vds-scripts/git_orchestrator/tests/__init__.py +0 -0
- package/tools/vds-scripts/git_orchestrator/tests/test_cli_settings.py +21 -0
- package/tools/vds-scripts/git_orchestrator/tests/test_integration.py +74 -0
- package/tools/vds-scripts/git_orchestrator/tests/test_manifest.py +79 -0
- package/tools/vds-scripts/git_orchestrator/tests/test_orchestrator.py +204 -0
- package/tools/vds-scripts/git_orchestrator/tests/test_public_api.py +236 -0
- package/tools/vds-scripts/git_orchestrator/tests/test_resilience.py +345 -0
- package/tools/vds-scripts/git_orchestrator/uv.lock +271 -0
- package/tools/vds-scripts/jira_orchestrator/README.md +770 -0
- package/tools/vds-scripts/jira_orchestrator/pyproject.toml +39 -0
- package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +1 -0
- package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1320 -0
- package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2271 -0
- package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/config.py +138 -0
- package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/errors.py +67 -0
- package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +65 -0
- package/tools/vds-scripts/jira_orchestrator/tests/__init__.py +1 -0
- package/tools/vds-scripts/jira_orchestrator/tests/conftest.py +86 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_bulk_operations.py +69 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_components.py +57 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_dashboard.py +117 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_issue_properties.py +54 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_remote_links.py +76 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_transitions.py +91 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_user_management.py +110 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_version_management.py +133 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_watchers.py +41 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_advanced_search.py +164 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_agile.py +256 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_application_properties.py +193 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_backlog.py +91 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_bulk_operations.py +277 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_cli.py +106 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_components.py +106 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_config.py +164 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_dashboard.py +122 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_discover_fields.py +207 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_filter_management.py +333 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_issue_archiving.py +164 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_issue_links.py +257 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_issue_properties.py +171 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_link_types.py +314 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_parse_set.py +37 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_permissions.py +273 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_reindex.py +81 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_remote_links.py +254 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_security_schemes.py +170 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_transitions_changelog.py +114 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_user_management.py +226 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_version_management.py +339 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_watchers.py +101 -0
- package/tools/vds-scripts/jira_orchestrator/tests/test_worklog.py +223 -0
- package/tools/vds-scripts/jira_orchestrator/uv.lock +738 -0
- package/tools/vds-scripts/mcp_server/Dockerfile +34 -0
- package/tools/vds-scripts/mcp_server/README.md +140 -0
- package/tools/vds-scripts/mcp_server/pyproject.toml +42 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/__init__.py +4 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/config.py +36 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/server.py +66 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/__init__.py +14 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +59 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
- package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
- package/tools/vds-scripts/mcp_server/tests/__init__.py +2 -0
- package/tools/vds-scripts/mcp_server/tests/conftest.py +29 -0
- package/tools/vds-scripts/mcp_server/tests/unit/__init__.py +2 -0
- package/tools/vds-scripts/mcp_server/tests/unit/test_bitbucket_tools.py +25 -0
- package/tools/vds-scripts/mcp_server/tests/unit/test_confluence_tools.py +25 -0
- package/tools/vds-scripts/mcp_server/tests/unit/test_git_tools.py +32 -0
- package/tools/vds-scripts/mcp_server/tests/unit/test_jira_tools.py +32 -0
- package/tools/vds-scripts/mcp_server/tests/verification/__init__.py +2 -0
- package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
- package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_jira_tools.py +37 -0
- package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_tool_registration.py +47 -0
- package/tools/vds-scripts/mcp_server/uv.lock +1032 -0
- package/tools/vds-scripts/mypy.ini +5 -0
- package/tools/vds-scripts/pyproject.toml +29 -0
- package/tools/vds-scripts/repo-manifest.yaml +273 -0
- package/tools/vds-scripts/repo-manifest.yaml.example +25 -0
- package/tools/vds-scripts/scripts/BRD-Validation-API.postman_collection.json +706 -0
- package/tools/vds-scripts/scripts/BRD-Validation-README.md +308 -0
- package/tools/vds-scripts/scripts/README.md +162 -0
- package/tools/vds-scripts/scripts/bootstrap_uv.sh +30 -0
- package/tools/vds-scripts/scripts/brd-validation-environment.json +51 -0
- package/tools/vds-scripts/scripts/brd-validation-test-results.json +13023 -0
- package/tools/vds-scripts/scripts/brd_coverage_report.json +276 -0
- package/tools/vds-scripts/scripts/create_memory_session.py +35 -0
- package/tools/vds-scripts/scripts/deployment/load_docker_images_offline.sh +90 -0
- package/tools/vds-scripts/scripts/final_completion_report.md +139 -0
- package/tools/vds-scripts/scripts/folder_structure_report.json +321 -0
- package/tools/vds-scripts/scripts/generate_completion_report.py +125 -0
- package/tools/vds-scripts/scripts/generate_intellij_modules.py +150 -0
- package/tools/vds-scripts/scripts/link_integrity_report.json +807 -0
- package/tools/vds-scripts/scripts/move_audit_artifact_pages.py +255 -0
- package/tools/vds-scripts/scripts/move_audit_artifact_pages_rest.py +165 -0
- package/tools/vds-scripts/scripts/move_wrong_dept_pages.py +216 -0
- package/tools/vds-scripts/scripts/save_intellij_memories.py +120 -0
- package/tools/vds-scripts/scripts/save_memories_to_vds_ai.py +83 -0
- package/tools/vds-scripts/scripts/save_memories_vds_style.py +129 -0
- package/tools/vds-scripts/scripts/search_intellij_memories.py +50 -0
- package/tools/vds-scripts/scripts/setup_intellij_workspace.py +65 -0
- package/tools/vds-scripts/scripts/target-state-automation/README.md +89 -0
- package/tools/vds-scripts/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
- package/tools/vds-scripts/scripts/target-state-automation/coordination.sh +114 -0
- package/tools/vds-scripts/scripts/target-state-automation/diagram_coordinator.sh +25 -0
- package/tools/vds-scripts/scripts/target-state-automation/docs_root.sh +22 -0
- package/tools/vds-scripts/scripts/target-state-automation/generate_diagrams.sh +22 -0
- package/tools/vds-scripts/scripts/target-state-automation/markdown_coordinator.sh +25 -0
- package/tools/vds-scripts/scripts/target-state-automation/progress_dashboard.sh +17 -0
- package/tools/vds-scripts/scripts/target-state-automation/schema_coordinator.sh +25 -0
- package/tools/vds-scripts/scripts/target-state-automation/sync_confluence.sh +30 -0
- package/tools/vds-scripts/scripts/target-state-automation/update_dependencies.sh +19 -0
- package/tools/vds-scripts/scripts/target-state-automation/validate_links.sh +86 -0
- package/tools/vds-scripts/scripts/target-state-automation/validate_markdown.sh +52 -0
- package/tools/vds-scripts/scripts/target-state-automation/validate_schemas.sh +26 -0
- package/tools/vds-scripts/scripts/target-state-automation/validate_structure.sh +98 -0
- package/tools/vds-scripts/scripts/update_modules_xml.py +190 -0
- package/tools/vds-scripts/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
- package/tools/vds-scripts/scripts/validate_brd_coverage.py +179 -0
- package/tools/vds-scripts/scripts/validate_folder_structure.py +240 -0
- package/tools/vds-scripts/scripts/validate_link_integrity.py +272 -0
- package/tools/vds-scripts/scripts/vds_sh_helpers.sh +180 -0
- package/tools/vds-scripts/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
- package/tools/vds-scripts/scripts/worktree_uv.sh +48 -0
- package/tools/vds-scripts/uv.lock +8 -0
- package/tools/vds-scripts/vds_cli/README.md +126 -0
- package/tools/vds-scripts/vds_cli/VERIFICATION_REPORT.md +41 -0
- package/tools/vds-scripts/vds_cli/pyproject.toml +38 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/__init__.py +3 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/cli.py +173 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/docs_sync.py +1203 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/env.py +41 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +198 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/router.py +93 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/sync_api.py +647 -0
- package/tools/vds-scripts/vds_cli/src/vds_cli/sync_service.py +266 -0
- package/tools/vds-scripts/vds_cli/tests/__init__.py +2 -0
- package/tools/vds-scripts/vds_cli/tests/conftest.py +49 -0
- package/tools/vds-scripts/vds_cli/tests/unit/__init__.py +2 -0
- package/tools/vds-scripts/vds_cli/tests/unit/test_cli.py +143 -0
- package/tools/vds-scripts/vds_cli/tests/unit/test_docs_sync.py +422 -0
- package/tools/vds-scripts/vds_cli/tests/unit/test_env.py +51 -0
- package/tools/vds-scripts/vds_cli/tests/unit/test_router.py +72 -0
- package/tools/vds-scripts/vds_cli/tests/unit/test_sync_api.py +357 -0
- package/tools/vds-scripts/vds_cli/tests/unit/test_sync_service.py +160 -0
- package/tools/vds-scripts/vds_cli/tests/verification/__init__.py +2 -0
- package/tools/vds-scripts/vds_cli/tests/verification/test_bitbucket_real.py +33 -0
- package/tools/vds-scripts/vds_cli/tests/verification/test_confluence_real.py +35 -0
- package/tools/vds-scripts/vds_cli/tests/verification/test_jira_real.py +41 -0
- package/tools/vds-scripts/vds_cli/uv.lock +524 -0
- package/tools/vds-scripts/vds_cli_common/README.md +190 -0
- package/tools/vds-scripts/vds_cli_common/pyproject.toml +92 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/__init__.py +34 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/completers.py +139 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/context.py +201 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/env.py +119 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/errors.py +318 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/output.py +284 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/paths.py +78 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/testing.py +213 -0
- package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/version.py +85 -0
- package/tools/vds-scripts/vds_cli_common/tests/__init__.py +1 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_completers.py +148 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_context.py +192 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_env.py +102 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_errors.py +186 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_output.py +229 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_paths.py +61 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_testing.py +138 -0
- package/tools/vds-scripts/vds_cli_common/tests/test_version.py +64 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Tests for common completers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from vds_cli_common.completers import (
|
|
6
|
+
complete_index,
|
|
7
|
+
complete_log_level,
|
|
8
|
+
complete_output_format,
|
|
9
|
+
complete_project,
|
|
10
|
+
complete_server,
|
|
11
|
+
complete_time_field,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestCompleteIndex:
|
|
16
|
+
"""Tests for complete_index function."""
|
|
17
|
+
|
|
18
|
+
def test_returns_matching_patterns(self) -> None:
|
|
19
|
+
"""Test returns patterns that match incomplete input."""
|
|
20
|
+
results = complete_index("logs")
|
|
21
|
+
assert "logs-*" in results
|
|
22
|
+
|
|
23
|
+
def test_returns_all_patterns_for_empty_input(self) -> None:
|
|
24
|
+
"""Test returns all patterns for empty input."""
|
|
25
|
+
results = complete_index("")
|
|
26
|
+
assert len(results) > 0
|
|
27
|
+
assert "logs-*" in results
|
|
28
|
+
assert "*" in results
|
|
29
|
+
|
|
30
|
+
def test_returns_empty_for_no_match(self) -> None:
|
|
31
|
+
"""Test returns empty list for no match."""
|
|
32
|
+
results = complete_index("xyz")
|
|
33
|
+
assert results == []
|
|
34
|
+
|
|
35
|
+
def test_includes_otel_patterns(self) -> None:
|
|
36
|
+
"""Test includes OpenTelemetry patterns."""
|
|
37
|
+
results = complete_index("otel")
|
|
38
|
+
assert "otel-logs-*" in results
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestCompleteTimeField:
|
|
42
|
+
"""Tests for complete_time_field function."""
|
|
43
|
+
|
|
44
|
+
def test_returns_matching_fields(self) -> None:
|
|
45
|
+
"""Test returns fields that match incomplete input."""
|
|
46
|
+
results = complete_time_field("@")
|
|
47
|
+
assert "@timestamp" in results
|
|
48
|
+
|
|
49
|
+
def test_returns_timestamp_fields(self) -> None:
|
|
50
|
+
"""Test returns timestamp-related fields."""
|
|
51
|
+
results = complete_time_field("time")
|
|
52
|
+
assert "timestamp" in results
|
|
53
|
+
|
|
54
|
+
def test_returns_empty_for_no_match(self) -> None:
|
|
55
|
+
"""Test returns empty list for no match."""
|
|
56
|
+
results = complete_time_field("xyz")
|
|
57
|
+
assert results == []
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TestCompleteProject:
|
|
61
|
+
"""Tests for complete_project function."""
|
|
62
|
+
|
|
63
|
+
def test_returns_matching_projects(self) -> None:
|
|
64
|
+
"""Test returns projects that match incomplete input."""
|
|
65
|
+
results = complete_project("ins")
|
|
66
|
+
assert "insurance" in results
|
|
67
|
+
|
|
68
|
+
def test_case_insensitive(self) -> None:
|
|
69
|
+
"""Test matching is case-insensitive."""
|
|
70
|
+
results = complete_project("LEP")
|
|
71
|
+
assert "lep" in results
|
|
72
|
+
|
|
73
|
+
def test_returns_all_projects_for_empty(self) -> None:
|
|
74
|
+
"""Test returns all projects for empty input."""
|
|
75
|
+
results = complete_project("")
|
|
76
|
+
assert "lep" in results
|
|
77
|
+
assert "insurance" in results
|
|
78
|
+
assert "saving" in results
|
|
79
|
+
assert "ekyc" in results
|
|
80
|
+
|
|
81
|
+
def test_returns_empty_for_no_match(self) -> None:
|
|
82
|
+
"""Test returns empty list for no match."""
|
|
83
|
+
results = complete_project("xyz")
|
|
84
|
+
assert results == []
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TestCompleteServer:
|
|
88
|
+
"""Tests for complete_server function."""
|
|
89
|
+
|
|
90
|
+
def test_returns_matching_servers(self) -> None:
|
|
91
|
+
"""Test returns servers that match incomplete input."""
|
|
92
|
+
results = complete_server("int")
|
|
93
|
+
assert "internal" in results
|
|
94
|
+
|
|
95
|
+
def test_case_insensitive(self) -> None:
|
|
96
|
+
"""Test matching is case-insensitive."""
|
|
97
|
+
results = complete_server("EXT")
|
|
98
|
+
assert "external" in results
|
|
99
|
+
|
|
100
|
+
def test_returns_all_servers_for_empty(self) -> None:
|
|
101
|
+
"""Test returns all servers for empty input."""
|
|
102
|
+
results = complete_server("")
|
|
103
|
+
assert "internal" in results
|
|
104
|
+
assert "external" in results
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestCompleteOutputFormat:
|
|
108
|
+
"""Tests for complete_output_format function."""
|
|
109
|
+
|
|
110
|
+
def test_returns_matching_formats(self) -> None:
|
|
111
|
+
"""Test returns formats that match incomplete input."""
|
|
112
|
+
results = complete_output_format("js")
|
|
113
|
+
assert "json" in results
|
|
114
|
+
|
|
115
|
+
def test_case_insensitive(self) -> None:
|
|
116
|
+
"""Test matching is case-insensitive."""
|
|
117
|
+
results = complete_output_format("JSON")
|
|
118
|
+
assert "json" in results
|
|
119
|
+
|
|
120
|
+
def test_returns_all_formats_for_empty(self) -> None:
|
|
121
|
+
"""Test returns all formats for empty input."""
|
|
122
|
+
results = complete_output_format("")
|
|
123
|
+
assert "json" in results
|
|
124
|
+
assert "table" in results
|
|
125
|
+
assert "csv" in results
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestCompleteLogLevel:
|
|
129
|
+
"""Tests for complete_log_level function."""
|
|
130
|
+
|
|
131
|
+
def test_returns_matching_levels(self) -> None:
|
|
132
|
+
"""Test returns levels that match incomplete input."""
|
|
133
|
+
results = complete_log_level("ERR")
|
|
134
|
+
assert "ERROR" in results
|
|
135
|
+
|
|
136
|
+
def test_case_insensitive(self) -> None:
|
|
137
|
+
"""Test matching is case-insensitive."""
|
|
138
|
+
results = complete_log_level("debug")
|
|
139
|
+
assert "DEBUG" in results
|
|
140
|
+
|
|
141
|
+
def test_returns_all_levels_for_empty(self) -> None:
|
|
142
|
+
"""Test returns all levels for empty input."""
|
|
143
|
+
results = complete_log_level("")
|
|
144
|
+
assert "DEBUG" in results
|
|
145
|
+
assert "INFO" in results
|
|
146
|
+
assert "WARNING" in results
|
|
147
|
+
assert "ERROR" in results
|
|
148
|
+
assert "CRITICAL" in results
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Tests for CLIContext."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
from vds_cli_common import CLIContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCLIContext:
|
|
12
|
+
"""Tests for CLIContext class."""
|
|
13
|
+
|
|
14
|
+
def test_default_values(self) -> None:
|
|
15
|
+
"""Test default values are set correctly."""
|
|
16
|
+
ctx = CLIContext()
|
|
17
|
+
assert ctx.json_only is False
|
|
18
|
+
assert ctx.quiet is False
|
|
19
|
+
assert ctx.structured_logs is False
|
|
20
|
+
assert ctx.no_input is False
|
|
21
|
+
assert ctx.no_color is False
|
|
22
|
+
assert ctx.force_color is False
|
|
23
|
+
|
|
24
|
+
def test_from_options_factory(self) -> None:
|
|
25
|
+
"""Test from_options factory method."""
|
|
26
|
+
ctx = CLIContext.from_options(
|
|
27
|
+
json_only=True,
|
|
28
|
+
quiet=True,
|
|
29
|
+
no_color=True,
|
|
30
|
+
)
|
|
31
|
+
assert ctx.json_only is True
|
|
32
|
+
assert ctx.quiet is True
|
|
33
|
+
assert ctx.no_color is True
|
|
34
|
+
|
|
35
|
+
def test_is_tty_detected_in_tty(self) -> None:
|
|
36
|
+
"""Test TTY is detected when stdout is a terminal."""
|
|
37
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
38
|
+
ctx = CLIContext()
|
|
39
|
+
assert ctx.is_tty is True
|
|
40
|
+
|
|
41
|
+
def test_is_tty_detected_in_pipe(self) -> None:
|
|
42
|
+
"""Test TTY is not detected when stdout is piped."""
|
|
43
|
+
with patch("sys.stdout.isatty", return_value=False):
|
|
44
|
+
ctx = CLIContext()
|
|
45
|
+
assert ctx.is_tty is False
|
|
46
|
+
|
|
47
|
+
def test_use_color_disabled_by_json_only(self) -> None:
|
|
48
|
+
"""Test json_only mode disables colors."""
|
|
49
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
50
|
+
ctx = CLIContext.from_options(json_only=True)
|
|
51
|
+
assert ctx.use_color is False
|
|
52
|
+
|
|
53
|
+
def test_use_color_disabled_by_no_color_flag(self) -> None:
|
|
54
|
+
"""Test --no-color flag disables colors."""
|
|
55
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
56
|
+
ctx = CLIContext.from_options(no_color=True)
|
|
57
|
+
assert ctx.use_color is False
|
|
58
|
+
|
|
59
|
+
def test_use_color_enabled_by_force_color_flag(self) -> None:
|
|
60
|
+
"""Test --color flag enables colors."""
|
|
61
|
+
with patch("sys.stdout.isatty", return_value=False):
|
|
62
|
+
ctx = CLIContext.from_options(force_color=True)
|
|
63
|
+
assert ctx.use_color is True
|
|
64
|
+
|
|
65
|
+
def test_no_color_flag_overrides_force_color(self) -> None:
|
|
66
|
+
"""Test --no-color flag takes precedence over --color."""
|
|
67
|
+
ctx = CLIContext.from_options(no_color=True, force_color=True)
|
|
68
|
+
assert ctx.use_color is False
|
|
69
|
+
|
|
70
|
+
def test_no_color_env_var_respected(self) -> None:
|
|
71
|
+
"""Test NO_COLOR environment variable disables colors."""
|
|
72
|
+
with patch("sys.stdout.isatty", return_value=True), patch.dict(os.environ, {"NO_COLOR": "1"}):
|
|
73
|
+
ctx = CLIContext()
|
|
74
|
+
assert ctx.use_color is False
|
|
75
|
+
|
|
76
|
+
def test_force_color_env_var_respected(self) -> None:
|
|
77
|
+
"""Test FORCE_COLOR environment variable enables colors."""
|
|
78
|
+
with patch("sys.stdout.isatty", return_value=False):
|
|
79
|
+
with patch.dict(os.environ, {"FORCE_COLOR": "1"}, clear=False):
|
|
80
|
+
# Clear NO_COLOR if present
|
|
81
|
+
env = os.environ.copy()
|
|
82
|
+
env.pop("NO_COLOR", None)
|
|
83
|
+
env["FORCE_COLOR"] = "1"
|
|
84
|
+
with patch.dict(os.environ, env, clear=True):
|
|
85
|
+
ctx = CLIContext()
|
|
86
|
+
assert ctx.use_color is True
|
|
87
|
+
|
|
88
|
+
def test_use_rich_output_in_tty(self) -> None:
|
|
89
|
+
"""Test Rich output is enabled in TTY."""
|
|
90
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
91
|
+
ctx = CLIContext()
|
|
92
|
+
assert ctx.use_rich_output is True
|
|
93
|
+
|
|
94
|
+
def test_use_rich_output_disabled_in_pipe(self) -> None:
|
|
95
|
+
"""Test Rich output is disabled when piped."""
|
|
96
|
+
with patch("sys.stdout.isatty", return_value=False):
|
|
97
|
+
ctx = CLIContext()
|
|
98
|
+
assert ctx.use_rich_output is False
|
|
99
|
+
|
|
100
|
+
def test_use_rich_output_disabled_by_json_only(self) -> None:
|
|
101
|
+
"""Test Rich output is disabled in json_only mode."""
|
|
102
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
103
|
+
ctx = CLIContext.from_options(json_only=True)
|
|
104
|
+
assert ctx.use_rich_output is False
|
|
105
|
+
|
|
106
|
+
def test_use_spinner_in_tty(self) -> None:
|
|
107
|
+
"""Test spinner is enabled in TTY."""
|
|
108
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
109
|
+
ctx = CLIContext()
|
|
110
|
+
assert ctx.use_spinner is True
|
|
111
|
+
|
|
112
|
+
def test_use_spinner_disabled_by_quiet(self) -> None:
|
|
113
|
+
"""Test spinner is disabled in quiet mode."""
|
|
114
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
115
|
+
ctx = CLIContext.from_options(quiet=True)
|
|
116
|
+
assert ctx.use_spinner is False
|
|
117
|
+
|
|
118
|
+
def test_use_progress_in_tty(self) -> None:
|
|
119
|
+
"""Test progress is enabled in TTY."""
|
|
120
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
121
|
+
ctx = CLIContext()
|
|
122
|
+
assert ctx.use_progress is True
|
|
123
|
+
|
|
124
|
+
def test_use_progress_disabled_by_quiet(self) -> None:
|
|
125
|
+
"""Test progress is disabled in quiet mode."""
|
|
126
|
+
with patch("sys.stdout.isatty", return_value=True):
|
|
127
|
+
ctx = CLIContext.from_options(quiet=True)
|
|
128
|
+
assert ctx.use_progress is False
|
|
129
|
+
|
|
130
|
+
def test_show_hints_enabled_by_default(self) -> None:
|
|
131
|
+
"""Test hints are shown by default."""
|
|
132
|
+
ctx = CLIContext()
|
|
133
|
+
assert ctx.show_hints is True
|
|
134
|
+
|
|
135
|
+
def test_show_hints_disabled_by_json_only(self) -> None:
|
|
136
|
+
"""Test hints are hidden in json_only mode."""
|
|
137
|
+
ctx = CLIContext.from_options(json_only=True)
|
|
138
|
+
assert ctx.show_hints is False
|
|
139
|
+
|
|
140
|
+
def test_show_hints_disabled_by_quiet(self) -> None:
|
|
141
|
+
"""Test hints are hidden in quiet mode."""
|
|
142
|
+
ctx = CLIContext.from_options(quiet=True)
|
|
143
|
+
assert ctx.show_hints is False
|
|
144
|
+
|
|
145
|
+
def test_console_property_returns_console(self) -> None:
|
|
146
|
+
"""Test console property returns Rich Console."""
|
|
147
|
+
ctx = CLIContext()
|
|
148
|
+
console = ctx.console
|
|
149
|
+
assert console is not None
|
|
150
|
+
# Should return same instance on second call
|
|
151
|
+
assert ctx.console is console
|
|
152
|
+
|
|
153
|
+
def test_err_console_property_returns_stderr_console(self) -> None:
|
|
154
|
+
"""Test err_console property returns Rich Console for stderr."""
|
|
155
|
+
ctx = CLIContext()
|
|
156
|
+
err_console = ctx.err_console
|
|
157
|
+
assert err_console is not None
|
|
158
|
+
# Should return same instance on second call
|
|
159
|
+
assert ctx.err_console is err_console
|
|
160
|
+
|
|
161
|
+
def test_from_typer_context_with_cli_context(self) -> None:
|
|
162
|
+
"""Test from_typer_context extracts CLIContext."""
|
|
163
|
+
from unittest.mock import MagicMock
|
|
164
|
+
|
|
165
|
+
original_ctx = CLIContext.from_options(json_only=True)
|
|
166
|
+
typer_ctx = MagicMock()
|
|
167
|
+
typer_ctx.obj = original_ctx
|
|
168
|
+
|
|
169
|
+
result = CLIContext.from_typer_context(typer_ctx)
|
|
170
|
+
assert result is original_ctx
|
|
171
|
+
|
|
172
|
+
def test_from_typer_context_with_dict(self) -> None:
|
|
173
|
+
"""Test from_typer_context extracts CLIContext from dict."""
|
|
174
|
+
from unittest.mock import MagicMock
|
|
175
|
+
|
|
176
|
+
original_ctx = CLIContext.from_options(quiet=True)
|
|
177
|
+
typer_ctx = MagicMock()
|
|
178
|
+
typer_ctx.obj = {"cli_ctx": original_ctx}
|
|
179
|
+
|
|
180
|
+
result = CLIContext.from_typer_context(typer_ctx)
|
|
181
|
+
assert result is original_ctx
|
|
182
|
+
|
|
183
|
+
def test_from_typer_context_returns_default(self) -> None:
|
|
184
|
+
"""Test from_typer_context returns default when not found."""
|
|
185
|
+
from unittest.mock import MagicMock
|
|
186
|
+
|
|
187
|
+
typer_ctx = MagicMock()
|
|
188
|
+
typer_ctx.obj = None
|
|
189
|
+
|
|
190
|
+
result = CLIContext.from_typer_context(typer_ctx)
|
|
191
|
+
assert isinstance(result, CLIContext)
|
|
192
|
+
assert result.json_only is False
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from vds_cli_common.env import DEFAULT_AUDIT_ENV_DEFAULTS, ensure_shared_env, load_shared_env
|
|
7
|
+
from vds_cli_common.paths import get_shared_env_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _write_env(path: Path, content: str) -> None:
|
|
11
|
+
path.write_text(content, encoding="utf-8")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_load_shared_env_first_file_wins_when_keys_overlap(tmp_path: Path, monkeypatch) -> None:
|
|
15
|
+
primary = tmp_path / "primary.env"
|
|
16
|
+
fallback = tmp_path / "fallback.env"
|
|
17
|
+
_write_env(primary, "TOKEN=from-primary\nPRIMARY_ONLY=1\n")
|
|
18
|
+
_write_env(fallback, "TOKEN=from-fallback\nFALLBACK_ONLY=1\n")
|
|
19
|
+
|
|
20
|
+
monkeypatch.delenv("TOKEN", raising=False)
|
|
21
|
+
monkeypatch.delenv("PRIMARY_ONLY", raising=False)
|
|
22
|
+
monkeypatch.delenv("FALLBACK_ONLY", raising=False)
|
|
23
|
+
|
|
24
|
+
merged = load_shared_env(path=primary, extra_paths=(fallback,))
|
|
25
|
+
|
|
26
|
+
assert merged["TOKEN"] == "from-primary"
|
|
27
|
+
assert merged["PRIMARY_ONLY"] == "1"
|
|
28
|
+
assert merged["FALLBACK_ONLY"] == "1"
|
|
29
|
+
assert "TOKEN" in merged
|
|
30
|
+
assert "PRIMARY_ONLY" in merged
|
|
31
|
+
assert "FALLBACK_ONLY" in merged
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_load_shared_env_does_not_override_existing_environ_by_default(tmp_path: Path, monkeypatch) -> None:
|
|
35
|
+
env_file = tmp_path / "shared.env"
|
|
36
|
+
_write_env(env_file, "TOKEN=from-file\n")
|
|
37
|
+
|
|
38
|
+
monkeypatch.setenv("TOKEN", "from-shell")
|
|
39
|
+
load_shared_env(path=env_file)
|
|
40
|
+
|
|
41
|
+
assert os.environ["TOKEN"] == "from-shell"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_load_shared_env_override_true_updates_existing_environ(tmp_path: Path, monkeypatch) -> None:
|
|
45
|
+
env_file = tmp_path / "shared.env"
|
|
46
|
+
_write_env(env_file, "TOKEN=from-file\n")
|
|
47
|
+
|
|
48
|
+
monkeypatch.setenv("TOKEN", "from-shell")
|
|
49
|
+
load_shared_env(path=env_file, override=True)
|
|
50
|
+
|
|
51
|
+
assert os.environ["TOKEN"] == "from-file"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_ensure_shared_env_creates_file_with_required_defaults(tmp_path: Path) -> None:
|
|
55
|
+
env_file = tmp_path / ".vds" / ".env"
|
|
56
|
+
created = ensure_shared_env(path=env_file)
|
|
57
|
+
|
|
58
|
+
assert created == env_file
|
|
59
|
+
content = env_file.read_text(encoding="utf-8")
|
|
60
|
+
for key, value in DEFAULT_AUDIT_ENV_DEFAULTS:
|
|
61
|
+
assert f"{key}={value}" in content
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_ensure_shared_env_appends_missing_defaults_without_overwriting(tmp_path: Path) -> None:
|
|
65
|
+
env_file = tmp_path / ".vds" / ".env"
|
|
66
|
+
env_file.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
_write_env(env_file, "VDS_AUDIT_STATE_DSN=postgresql://custom/db\nTOKEN=kept\n")
|
|
68
|
+
|
|
69
|
+
ensure_shared_env(path=env_file)
|
|
70
|
+
|
|
71
|
+
values = {}
|
|
72
|
+
for line in env_file.read_text(encoding="utf-8").splitlines():
|
|
73
|
+
if "=" in line and not line.lstrip().startswith("#"):
|
|
74
|
+
key, value = line.split("=", 1)
|
|
75
|
+
values[key] = value
|
|
76
|
+
assert values["VDS_AUDIT_STATE_DSN"] == "postgresql://custom/db"
|
|
77
|
+
assert values["TOKEN"] == "kept"
|
|
78
|
+
for key, _ in DEFAULT_AUDIT_ENV_DEFAULTS:
|
|
79
|
+
assert key in values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_get_shared_env_path_uses_override(monkeypatch, tmp_path: Path) -> None:
|
|
83
|
+
override = tmp_path / "envs" / "shared.env"
|
|
84
|
+
monkeypatch.setenv("VDS_ENV_FILE", str(override))
|
|
85
|
+
|
|
86
|
+
assert get_shared_env_path() == override.resolve()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_load_shared_env_uses_runtime_env_file_override_without_stale_defaults(
|
|
90
|
+
monkeypatch, tmp_path: Path
|
|
91
|
+
) -> None:
|
|
92
|
+
override = tmp_path / "portable" / ".env"
|
|
93
|
+
override.parent.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
_write_env(override, "TOKEN=from-runtime-override\n")
|
|
95
|
+
|
|
96
|
+
monkeypatch.setenv("VDS_ENV_FILE", str(override))
|
|
97
|
+
monkeypatch.delenv("TOKEN", raising=False)
|
|
98
|
+
|
|
99
|
+
merged = load_shared_env()
|
|
100
|
+
|
|
101
|
+
assert merged["TOKEN"] == "from-runtime-override"
|
|
102
|
+
assert os.environ["TOKEN"] == "from-runtime-override"
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""Tests for ErrorHandler and ExitCodes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from vds_cli_common import CLIContext, ErrorHandler, ExitCodes, OutputManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestExitCodes:
|
|
14
|
+
"""Tests for ExitCodes enum."""
|
|
15
|
+
|
|
16
|
+
def test_success_is_zero(self) -> None:
|
|
17
|
+
"""Test SUCCESS is 0."""
|
|
18
|
+
assert ExitCodes.SUCCESS == 0
|
|
19
|
+
|
|
20
|
+
def test_general_error_is_one(self) -> None:
|
|
21
|
+
"""Test GENERAL_ERROR is 1."""
|
|
22
|
+
assert ExitCodes.GENERAL_ERROR == 1
|
|
23
|
+
|
|
24
|
+
def test_all_codes_are_integers(self) -> None:
|
|
25
|
+
"""Test all exit codes are integers."""
|
|
26
|
+
for code in ExitCodes:
|
|
27
|
+
assert isinstance(code.value, int)
|
|
28
|
+
|
|
29
|
+
def test_codes_are_unique(self) -> None:
|
|
30
|
+
"""Test all exit codes are unique."""
|
|
31
|
+
values = [code.value for code in ExitCodes]
|
|
32
|
+
assert len(values) == len(set(values))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestErrorHandler:
|
|
36
|
+
"""Tests for ErrorHandler class."""
|
|
37
|
+
|
|
38
|
+
@pytest.fixture
|
|
39
|
+
def handler(self) -> ErrorHandler:
|
|
40
|
+
"""Create an ErrorHandler for testing."""
|
|
41
|
+
ctx = CLIContext.from_options(json_only=True)
|
|
42
|
+
output = OutputManager(ctx)
|
|
43
|
+
return ErrorHandler(output, valid_commands=["search", "count", "ping", "export"])
|
|
44
|
+
|
|
45
|
+
def test_suggest_command_finds_match(self, handler: ErrorHandler) -> None:
|
|
46
|
+
"""Test suggest_command finds close matches."""
|
|
47
|
+
assert handler.suggest_command("serch") == "search"
|
|
48
|
+
assert handler.suggest_command("cout") == "count"
|
|
49
|
+
assert handler.suggest_command("pimg") == "ping"
|
|
50
|
+
|
|
51
|
+
def test_suggest_command_no_match(self, handler: ErrorHandler) -> None:
|
|
52
|
+
"""Test suggest_command returns None for no match."""
|
|
53
|
+
assert handler.suggest_command("xyz") is None
|
|
54
|
+
assert handler.suggest_command("foo") is None
|
|
55
|
+
|
|
56
|
+
def test_suggest_command_exact_match(self, handler: ErrorHandler) -> None:
|
|
57
|
+
"""Test suggest_command works with exact match."""
|
|
58
|
+
assert handler.suggest_command("search") == "search"
|
|
59
|
+
|
|
60
|
+
def test_suggest_command_custom_cutoff(self, handler: ErrorHandler) -> None:
|
|
61
|
+
"""Test suggest_command respects cutoff parameter."""
|
|
62
|
+
# With high cutoff, weak matches should fail
|
|
63
|
+
assert handler.suggest_command("s", cutoff=0.9) is None
|
|
64
|
+
# With low cutoff, weak matches might succeed
|
|
65
|
+
result = handler.suggest_command("sear", cutoff=0.5)
|
|
66
|
+
assert result is not None
|
|
67
|
+
|
|
68
|
+
def test_handle_unknown_command_exits(self, handler: ErrorHandler) -> None:
|
|
69
|
+
"""Test handle_unknown_command exits with USAGE_ERROR."""
|
|
70
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
71
|
+
handler.handle_unknown_command("foo")
|
|
72
|
+
assert exc_info.value.exit_code == ExitCodes.USAGE_ERROR
|
|
73
|
+
|
|
74
|
+
def test_handle_unknown_command_with_suggestion(self, handler: ErrorHandler) -> None:
|
|
75
|
+
"""Test handle_unknown_command provides suggestion."""
|
|
76
|
+
handler.output = MagicMock()
|
|
77
|
+
with pytest.raises(typer.Exit):
|
|
78
|
+
handler.handle_unknown_command("serch")
|
|
79
|
+
# Should have called output_error
|
|
80
|
+
handler.output.output_error.assert_called_once()
|
|
81
|
+
|
|
82
|
+
def test_handle_config_error_exits(self, handler: ErrorHandler) -> None:
|
|
83
|
+
"""Test handle_config_error exits with CONFIG_ERROR."""
|
|
84
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
85
|
+
handler.handle_config_error("Missing config")
|
|
86
|
+
assert exc_info.value.exit_code == ExitCodes.CONFIG_ERROR
|
|
87
|
+
|
|
88
|
+
def test_handle_config_error_with_missing_vars(self, handler: ErrorHandler) -> None:
|
|
89
|
+
"""Test handle_config_error includes missing variables."""
|
|
90
|
+
handler.output = MagicMock()
|
|
91
|
+
with pytest.raises(typer.Exit):
|
|
92
|
+
handler.handle_config_error(
|
|
93
|
+
"Config error",
|
|
94
|
+
missing_vars=["VAR1", "VAR2"],
|
|
95
|
+
)
|
|
96
|
+
handler.output.output_error.assert_called_once()
|
|
97
|
+
call_kwargs = handler.output.output_error.call_args[1]
|
|
98
|
+
assert "missing_variables" in call_kwargs["details"]
|
|
99
|
+
|
|
100
|
+
def test_handle_auth_error_exits(self, handler: ErrorHandler) -> None:
|
|
101
|
+
"""Test handle_auth_error exits with AUTH_ERROR."""
|
|
102
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
103
|
+
handler.handle_auth_error("Invalid credentials")
|
|
104
|
+
assert exc_info.value.exit_code == ExitCodes.AUTH_ERROR
|
|
105
|
+
|
|
106
|
+
def test_handle_permission_error_exits(self, handler: ErrorHandler) -> None:
|
|
107
|
+
"""Test handle_permission_error exits with PERMISSION_ERROR."""
|
|
108
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
109
|
+
handler.handle_permission_error("Access denied")
|
|
110
|
+
assert exc_info.value.exit_code == ExitCodes.PERMISSION_ERROR
|
|
111
|
+
|
|
112
|
+
def test_handle_permission_error_with_roles(self, handler: ErrorHandler) -> None:
|
|
113
|
+
"""Test handle_permission_error includes role information."""
|
|
114
|
+
handler.output = MagicMock()
|
|
115
|
+
with pytest.raises(typer.Exit):
|
|
116
|
+
handler.handle_permission_error(
|
|
117
|
+
"Access denied",
|
|
118
|
+
required_role="admin",
|
|
119
|
+
current_role="viewer",
|
|
120
|
+
)
|
|
121
|
+
call_kwargs = handler.output.output_error.call_args[1]
|
|
122
|
+
assert call_kwargs["details"]["required_role"] == "admin"
|
|
123
|
+
assert call_kwargs["details"]["current_role"] == "viewer"
|
|
124
|
+
|
|
125
|
+
def test_handle_network_error_exits(self, handler: ErrorHandler) -> None:
|
|
126
|
+
"""Test handle_network_error exits with NETWORK_ERROR."""
|
|
127
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
128
|
+
handler.handle_network_error("Connection failed")
|
|
129
|
+
assert exc_info.value.exit_code == ExitCodes.NETWORK_ERROR
|
|
130
|
+
|
|
131
|
+
def test_handle_network_error_with_url(self, handler: ErrorHandler) -> None:
|
|
132
|
+
"""Test handle_network_error includes URL."""
|
|
133
|
+
handler.output = MagicMock()
|
|
134
|
+
with pytest.raises(typer.Exit):
|
|
135
|
+
handler.handle_network_error(
|
|
136
|
+
"Connection failed",
|
|
137
|
+
url="http://example.com",
|
|
138
|
+
)
|
|
139
|
+
call_kwargs = handler.output.output_error.call_args[1]
|
|
140
|
+
assert call_kwargs["details"]["url"] == "http://example.com"
|
|
141
|
+
|
|
142
|
+
def test_handle_not_found_exits(self, handler: ErrorHandler) -> None:
|
|
143
|
+
"""Test handle_not_found exits with NOT_FOUND_ERROR."""
|
|
144
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
145
|
+
handler.handle_not_found("Index", "logs-*")
|
|
146
|
+
assert exc_info.value.exit_code == ExitCodes.NOT_FOUND_ERROR
|
|
147
|
+
|
|
148
|
+
def test_handle_validation_error_exits(self, handler: ErrorHandler) -> None:
|
|
149
|
+
"""Test handle_validation_error exits with VALIDATION_ERROR."""
|
|
150
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
151
|
+
handler.handle_validation_error("Invalid input")
|
|
152
|
+
assert exc_info.value.exit_code == ExitCodes.VALIDATION_ERROR
|
|
153
|
+
|
|
154
|
+
def test_handle_validation_error_with_field(self, handler: ErrorHandler) -> None:
|
|
155
|
+
"""Test handle_validation_error includes field information."""
|
|
156
|
+
handler.output = MagicMock()
|
|
157
|
+
with pytest.raises(typer.Exit):
|
|
158
|
+
handler.handle_validation_error(
|
|
159
|
+
"Invalid input",
|
|
160
|
+
field="index",
|
|
161
|
+
expected="string",
|
|
162
|
+
)
|
|
163
|
+
call_kwargs = handler.output.output_error.call_args[1]
|
|
164
|
+
assert call_kwargs["details"]["field"] == "index"
|
|
165
|
+
assert call_kwargs["details"]["expected"] == "string"
|
|
166
|
+
|
|
167
|
+
def test_handle_general_error_exits(self, handler: ErrorHandler) -> None:
|
|
168
|
+
"""Test handle_general_error exits with GENERAL_ERROR."""
|
|
169
|
+
with pytest.raises(typer.Exit) as exc_info:
|
|
170
|
+
handler.handle_general_error("Something went wrong")
|
|
171
|
+
assert exc_info.value.exit_code == ExitCodes.GENERAL_ERROR
|
|
172
|
+
|
|
173
|
+
def test_handle_general_error_with_hint(self, handler: ErrorHandler) -> None:
|
|
174
|
+
"""Test handle_general_error includes hint."""
|
|
175
|
+
handler.output = MagicMock()
|
|
176
|
+
with pytest.raises(typer.Exit):
|
|
177
|
+
handler.handle_general_error(
|
|
178
|
+
"Error",
|
|
179
|
+
hint="Try again",
|
|
180
|
+
details={"key": "value"},
|
|
181
|
+
)
|
|
182
|
+
handler.output.output_error.assert_called_once_with(
|
|
183
|
+
"Error",
|
|
184
|
+
hint="Try again",
|
|
185
|
+
details={"key": "value"},
|
|
186
|
+
)
|