@ngocsangairvds/vsaf 3.1.27 → 3.2.2
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 +65 -39
- 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,138 @@
|
|
|
1
|
+
"""Strongly-typed Jira settings using pydantic-settings (SDK-only)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Iterable, Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from pydantic import AnyHttpUrl, Field, field_validator
|
|
11
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
12
|
+
|
|
13
|
+
_PROJECT_ROOT = Path(__file__).resolve().parents[3]
|
|
14
|
+
_SCRIPTS_DIR = _PROJECT_ROOT.parent
|
|
15
|
+
|
|
16
|
+
DEFAULT_ENV_PATHS = (
|
|
17
|
+
Path.home() / ".vds" / ".env",
|
|
18
|
+
_SCRIPTS_DIR / ".env",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_REQUIRED_KEYS: tuple[str, ...] = (
|
|
22
|
+
"VDS_USERNAME",
|
|
23
|
+
"VDS_PASSWORD",
|
|
24
|
+
"JIRA_TOKEN",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_OPTIONAL_KEYS: tuple[str, ...] = ("JIRA_BASE_URL",)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SettingsError(RuntimeError):
|
|
31
|
+
"""Raised when the environment configuration cannot be loaded."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class JiraSettings(BaseSettings):
|
|
35
|
+
model_config = SettingsConfigDict(env_file=None, env_prefix="", case_sensitive=True, populate_by_name=True)
|
|
36
|
+
|
|
37
|
+
username: str | None = Field(default=None, alias="VDS_USERNAME")
|
|
38
|
+
password: str | None = Field(default=None, alias="VDS_PASSWORD")
|
|
39
|
+
|
|
40
|
+
base_url: str = Field(default="http://jira.digital.vn", alias="JIRA_BASE_URL")
|
|
41
|
+
|
|
42
|
+
# Optional: PAT/token if available for Jira Server/DC
|
|
43
|
+
token: str | None = Field(default=None, alias="JIRA_TOKEN")
|
|
44
|
+
|
|
45
|
+
def require_basic_or_token(self) -> None:
|
|
46
|
+
if not ((self.username and self.password) or self.token):
|
|
47
|
+
raise ValueError("Missing credentials: set VDS_USERNAME/VDS_PASSWORD or JIRA_TOKEN")
|
|
48
|
+
|
|
49
|
+
@field_validator("base_url")
|
|
50
|
+
@classmethod
|
|
51
|
+
def _validate_base_url(cls, value: str) -> str:
|
|
52
|
+
AnyHttpUrl(value)
|
|
53
|
+
return value.rstrip("/")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class EnvSnapshot:
|
|
58
|
+
"""A snapshot of relevant environment variables."""
|
|
59
|
+
|
|
60
|
+
values: Mapping[str, str]
|
|
61
|
+
source: Path | str | None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _parse_env_file(env_path: Path) -> dict[str, str]:
|
|
65
|
+
if not env_path.exists():
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
result: dict[str, str] = {}
|
|
69
|
+
for raw_line in env_path.read_text().splitlines():
|
|
70
|
+
line = raw_line.strip()
|
|
71
|
+
if not line or line.startswith("#"):
|
|
72
|
+
continue
|
|
73
|
+
if "=" not in line:
|
|
74
|
+
continue
|
|
75
|
+
key, raw_value = line.split("=", 1)
|
|
76
|
+
key = key.strip()
|
|
77
|
+
value = raw_value.strip().strip('"').strip("'")
|
|
78
|
+
if key:
|
|
79
|
+
result[key] = value
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _merge_env_sources(*snapshots: EnvSnapshot) -> dict[str, str]:
|
|
84
|
+
merged: dict[str, str] = {}
|
|
85
|
+
for snapshot in snapshots:
|
|
86
|
+
merged.update(snapshot.values)
|
|
87
|
+
return merged
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _filter_keys(source: Mapping[str, str]) -> dict[str, str]:
|
|
91
|
+
relevant_keys = set(_REQUIRED_KEYS + _OPTIONAL_KEYS)
|
|
92
|
+
return {key: value for key, value in source.items() if key in relevant_keys}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _resolve_env_paths(env_path: Path | None) -> Iterable[Path]:
|
|
96
|
+
if env_path is not None:
|
|
97
|
+
return (env_path,)
|
|
98
|
+
return DEFAULT_ENV_PATHS
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def load_settings(
|
|
102
|
+
*,
|
|
103
|
+
env_path: Path | None = None,
|
|
104
|
+
overrides: Mapping[str, str] | None = None,
|
|
105
|
+
strict: bool = False,
|
|
106
|
+
) -> JiraSettings:
|
|
107
|
+
"""Load orchestrator settings from the environment and optional .env file."""
|
|
108
|
+
|
|
109
|
+
snapshots = []
|
|
110
|
+
for candidate in _resolve_env_paths(env_path):
|
|
111
|
+
snapshots.append(
|
|
112
|
+
EnvSnapshot(
|
|
113
|
+
values=_filter_keys(_parse_env_file(candidate)), source=candidate if candidate.exists() else None
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
snapshots.append(EnvSnapshot(values=_filter_keys(os.environ), source="os.environ"))
|
|
118
|
+
|
|
119
|
+
if overrides:
|
|
120
|
+
snapshots.append(EnvSnapshot(values=_filter_keys(overrides), source="overrides"))
|
|
121
|
+
|
|
122
|
+
merged = _merge_env_sources(*snapshots)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
settings = JiraSettings.model_validate(merged)
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
raise SettingsError(str(exc)) from exc
|
|
128
|
+
|
|
129
|
+
if strict:
|
|
130
|
+
try:
|
|
131
|
+
settings.require_basic_or_token()
|
|
132
|
+
except ValueError as exc:
|
|
133
|
+
raise SettingsError(str(exc)) from exc
|
|
134
|
+
|
|
135
|
+
return settings
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
__all__ = ["JiraSettings", "SettingsError", "load_settings", "DEFAULT_ENV_PATHS"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Domain errors for Jira adapter (consistent with VDS orchestrators)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JiraAdapterError(RuntimeError):
|
|
9
|
+
"""Base class for Jira adapter errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, *, context: dict[str, Any] | None = None) -> None:
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
self.context = context or {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JiraAuthError(JiraAdapterError):
|
|
17
|
+
"""Raised when authentication fails (401/403)."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class JiraNotFound(JiraAdapterError):
|
|
23
|
+
"""Raised when a resource is not found (404)."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class JiraRateLimited(JiraAdapterError):
|
|
29
|
+
"""Raised when rate limited by JIRA (429)."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, message: str, *, retry_after: float | None = None, context: dict[str, Any] | None = None
|
|
33
|
+
) -> None:
|
|
34
|
+
super().__init__(message, context=context)
|
|
35
|
+
self.retry_after = retry_after
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class JiraConflictError(JiraAdapterError):
|
|
39
|
+
"""Raised when there's a conflict (409)."""
|
|
40
|
+
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class JiraResponseError(JiraAdapterError):
|
|
45
|
+
"""Raised for other HTTP errors (4xx/5xx)."""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self, message: str, *, status_code: int | None = None, context: dict[str, Any] | None = None
|
|
49
|
+
) -> None:
|
|
50
|
+
super().__init__(message, context=context)
|
|
51
|
+
self.status_code = status_code
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class JiraTransportError(JiraAdapterError):
|
|
55
|
+
"""Raised for network/transport issues."""
|
|
56
|
+
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class JiraRetryError(JiraAdapterError):
|
|
61
|
+
"""Raised when retry attempts are exhausted."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self, message: str, *, attempts: int | None = None, context: dict[str, Any] | None = None
|
|
65
|
+
) -> None:
|
|
66
|
+
super().__init__(message, context=context)
|
|
67
|
+
self.attempts = attempts
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Run reporting utilities for the Jira orchestrator."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import UTC, datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
TIMESTAMP_FMT = "%Y-%m-%dT%H-%M-%SZ"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(slots=True)
|
|
13
|
+
class RunSummary:
|
|
14
|
+
command: str
|
|
15
|
+
args: list[str]
|
|
16
|
+
exit_code: int
|
|
17
|
+
duration_ms: int
|
|
18
|
+
started_at: datetime
|
|
19
|
+
finished_at: datetime
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def success(self) -> bool:
|
|
23
|
+
return self.exit_code == 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ensure_directory(base: Path, timestamp: datetime) -> Path:
|
|
27
|
+
day_dir = base / str(timestamp.year) / f"{timestamp.month:02d}" / f"{timestamp.day:02d}"
|
|
28
|
+
day_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
return day_dir
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def write_reports(summary: RunSummary, *, base_dir: Path, include_markdown: bool = True) -> Path:
|
|
33
|
+
"""Persist JSON (and optional Markdown) reports and return JSON path."""
|
|
34
|
+
timestamp = summary.finished_at.astimezone(UTC)
|
|
35
|
+
directory = _ensure_directory(base_dir, timestamp)
|
|
36
|
+
slug = f"{timestamp.strftime(TIMESTAMP_FMT)}_{summary.command}"
|
|
37
|
+
|
|
38
|
+
json_path = directory / f"{slug}.json"
|
|
39
|
+
json_payload = {
|
|
40
|
+
"command": summary.command,
|
|
41
|
+
"args": summary.args,
|
|
42
|
+
"exit_code": summary.exit_code,
|
|
43
|
+
"duration_ms": summary.duration_ms,
|
|
44
|
+
"started_at": summary.started_at.astimezone(UTC).isoformat().replace("+00:00", "Z"),
|
|
45
|
+
"finished_at": summary.finished_at.astimezone(UTC).isoformat().replace("+00:00", "Z"),
|
|
46
|
+
"success": summary.success,
|
|
47
|
+
}
|
|
48
|
+
json_path.write_text(json.dumps(json_payload, indent=2, sort_keys=True))
|
|
49
|
+
|
|
50
|
+
if include_markdown:
|
|
51
|
+
md_path = directory / f"{slug}.md"
|
|
52
|
+
status = "✅ Success" if summary.success else "❌ Failure"
|
|
53
|
+
md_lines = [
|
|
54
|
+
f"# Jira Run Report — {summary.command}",
|
|
55
|
+
"",
|
|
56
|
+
f"- **Status**: {status} (exit code {summary.exit_code})",
|
|
57
|
+
f"- **Duration**: {summary.duration_ms} ms",
|
|
58
|
+
f"- **Started**: {json_payload['started_at']}",
|
|
59
|
+
f"- **Finished**: {json_payload['finished_at']}",
|
|
60
|
+
]
|
|
61
|
+
if summary.args:
|
|
62
|
+
md_lines.append(f"- **Arguments**: {' '.join(summary.args)}")
|
|
63
|
+
md_path.write_text("\n".join(md_lines) + "\n")
|
|
64
|
+
|
|
65
|
+
return json_path
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Test configuration and fixtures for Jira orchestrator tests."""
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from vds_jira_orchestrator.config import JiraSettings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def clear_runtime_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
10
|
+
"""Clear runtime environment variables for testing."""
|
|
11
|
+
for key in (
|
|
12
|
+
"VDS_USERNAME",
|
|
13
|
+
"VDS_PASSWORD",
|
|
14
|
+
"JIRA_TOKEN",
|
|
15
|
+
"JIRA_BASE_URL",
|
|
16
|
+
):
|
|
17
|
+
monkeypatch.delenv(key, raising=False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write_env(tmp_path: Path, content: str) -> Path:
|
|
21
|
+
"""Write a temporary .env file for testing."""
|
|
22
|
+
env_path = tmp_path / ".env"
|
|
23
|
+
env_path.write_text(content)
|
|
24
|
+
return env_path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def mock_jira_settings() -> JiraSettings:
|
|
29
|
+
"""Create mock Jira settings for testing."""
|
|
30
|
+
return JiraSettings(
|
|
31
|
+
VDS_USERNAME="test_user",
|
|
32
|
+
VDS_PASSWORD="test_pass",
|
|
33
|
+
JIRA_TOKEN="test_token",
|
|
34
|
+
JIRA_BASE_URL="https://jira.test.com",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture
|
|
39
|
+
def sample_issue_response() -> dict[str, object]:
|
|
40
|
+
"""Sample Jira issue response."""
|
|
41
|
+
return {
|
|
42
|
+
"id": "10001",
|
|
43
|
+
"key": "TEST-123",
|
|
44
|
+
"fields": {
|
|
45
|
+
"summary": "Test Issue",
|
|
46
|
+
"description": "This is a test issue",
|
|
47
|
+
"status": {"name": "Open"},
|
|
48
|
+
"priority": {"name": "Medium"},
|
|
49
|
+
"issuetype": {"name": "Task"},
|
|
50
|
+
"project": {"key": "TEST", "name": "Test Project"},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def sample_project_response() -> dict[str, object]:
|
|
57
|
+
"""Sample Jira project response."""
|
|
58
|
+
return {
|
|
59
|
+
"id": "10000",
|
|
60
|
+
"key": "TEST",
|
|
61
|
+
"name": "Test Project",
|
|
62
|
+
"description": "A test project",
|
|
63
|
+
"projectTypeKey": "software",
|
|
64
|
+
"lead": {"displayName": "Test User"},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.fixture
|
|
69
|
+
def sample_search_results() -> dict[str, object]:
|
|
70
|
+
"""Sample Jira search results."""
|
|
71
|
+
return {
|
|
72
|
+
"startAt": 0,
|
|
73
|
+
"maxResults": 50,
|
|
74
|
+
"total": 1,
|
|
75
|
+
"issues": [
|
|
76
|
+
{
|
|
77
|
+
"id": "10001",
|
|
78
|
+
"key": "TEST-123",
|
|
79
|
+
"fields": {
|
|
80
|
+
"summary": "Test Issue",
|
|
81
|
+
"status": {"name": "Open"},
|
|
82
|
+
"priority": {"name": "Medium"},
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from unittest.mock import Mock, patch
|
|
2
|
+
|
|
3
|
+
from vds_jira_orchestrator.adapter import JiraAdapter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
7
|
+
def test_get_all_agile_boards_unwraps_values_list(mock_jira: Mock) -> None:
|
|
8
|
+
client = Mock()
|
|
9
|
+
client.get_all_agile_boards.return_value = {
|
|
10
|
+
"maxResults": 50,
|
|
11
|
+
"startAt": 0,
|
|
12
|
+
"isLast": True,
|
|
13
|
+
"values": [{"id": 1, "name": "NTTC Scrum Board"}],
|
|
14
|
+
}
|
|
15
|
+
mock_jira.return_value = client
|
|
16
|
+
|
|
17
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
18
|
+
result = adapter.get_all_agile_boards(project_key="NTTC", limit=50)
|
|
19
|
+
|
|
20
|
+
assert result == [{"id": 1, "name": "NTTC Scrum Board"}]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
24
|
+
def test_get_issues_for_board_unwraps_issues_list(mock_jira: Mock) -> None:
|
|
25
|
+
client = Mock()
|
|
26
|
+
client.get_issues_for_board.return_value = {
|
|
27
|
+
"startAt": 0,
|
|
28
|
+
"maxResults": 50,
|
|
29
|
+
"total": 1,
|
|
30
|
+
"issues": [{"key": "NTTC-5705"}],
|
|
31
|
+
}
|
|
32
|
+
mock_jira.return_value = client
|
|
33
|
+
|
|
34
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
35
|
+
result = adapter.get_issues_for_board(1)
|
|
36
|
+
|
|
37
|
+
assert result == [{"key": "NTTC-5705"}]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
41
|
+
def test_get_all_sprints_from_board_unwraps_values_list(mock_jira: Mock) -> None:
|
|
42
|
+
client = Mock()
|
|
43
|
+
client.get_all_sprints_from_board.return_value = {
|
|
44
|
+
"maxResults": 50,
|
|
45
|
+
"startAt": 0,
|
|
46
|
+
"isLast": True,
|
|
47
|
+
"values": [{"id": 101, "name": "Sprint 1"}],
|
|
48
|
+
}
|
|
49
|
+
mock_jira.return_value = client
|
|
50
|
+
|
|
51
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
52
|
+
result = adapter.get_all_sprints_from_board(1)
|
|
53
|
+
|
|
54
|
+
assert result == [{"id": 101, "name": "Sprint 1"}]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from vds_jira_orchestrator.adapter import JiraAdapter, JiraAdapterError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def mock_adapter() -> JiraAdapter:
|
|
11
|
+
"""Fixture for a JiraAdapter."""
|
|
12
|
+
with patch("vds_jira_orchestrator.adapter.Jira") as mock_jira_class:
|
|
13
|
+
mock_client = MagicMock()
|
|
14
|
+
mock_jira_class.return_value = mock_client
|
|
15
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
16
|
+
adapter._client = mock_client
|
|
17
|
+
return adapter
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_bulk_update_issue_field_calls_client(mock_adapter: JiraAdapter) -> None:
|
|
21
|
+
"""Test that bulk_update_issue_field calls the SDK client with correct parameters."""
|
|
22
|
+
mock_adapter._client.bulk_update_issue_field.return_value = {"updated": 2, "errors": []}
|
|
23
|
+
result = mock_adapter.bulk_update_issue_field(key_list=["TEST-1", "TEST-2"], fields="*all")
|
|
24
|
+
mock_adapter._client.bulk_update_issue_field.assert_called_once_with(["TEST-1", "TEST-2"], fields="*all")
|
|
25
|
+
assert result["updated"] == 2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_bulk_update_issue_field_with_fields(mock_adapter: JiraAdapter) -> None:
|
|
29
|
+
"""Test bulk_update_issue_field with specific fields."""
|
|
30
|
+
mock_adapter._client.bulk_update_issue_field.return_value = {"updated": 1, "errors": []}
|
|
31
|
+
fields_json = '{"summary": "Updated summary"}'
|
|
32
|
+
result = mock_adapter.bulk_update_issue_field(key_list=["TEST-1"], fields=fields_json)
|
|
33
|
+
mock_adapter._client.bulk_update_issue_field.assert_called_once_with(["TEST-1"], fields=fields_json)
|
|
34
|
+
assert result["updated"] == 1
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_bulk_update_issue_field_handles_error(mock_adapter: JiraAdapter) -> None:
|
|
38
|
+
"""Test that bulk_update_issue_field handles errors from the SDK client."""
|
|
39
|
+
mock_adapter._client.bulk_update_issue_field.side_effect = Exception("API Error")
|
|
40
|
+
with pytest.raises(JiraAdapterError, match="API Error"):
|
|
41
|
+
mock_adapter.bulk_update_issue_field(key_list=["TEST-1"], fields="*all")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_issue_field_value_append_calls_client(mock_adapter: JiraAdapter) -> None:
|
|
45
|
+
"""Test that issue_field_value_append calls the SDK client with correct parameters."""
|
|
46
|
+
mock_adapter._client.issue_field_value_append.return_value = {"id": "10000", "value": "appended"}
|
|
47
|
+
value_dict = {"name": "test-value"}
|
|
48
|
+
result = mock_adapter.issue_field_value_append(issue_id_or_key="TEST-1", field="customfield_10000", value=value_dict)
|
|
49
|
+
mock_adapter._client.issue_field_value_append.assert_called_once_with("TEST-1", "customfield_10000", value_dict, notify_users=True)
|
|
50
|
+
assert result["id"] == "10000"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_issue_field_value_append_with_notify_false(mock_adapter: JiraAdapter) -> None:
|
|
54
|
+
"""Test issue_field_value_append with notify_users=False."""
|
|
55
|
+
mock_adapter._client.issue_field_value_append.return_value = {"id": "10001", "value": "appended"}
|
|
56
|
+
value_dict = {"name": "another-value"}
|
|
57
|
+
result = mock_adapter.issue_field_value_append(
|
|
58
|
+
issue_id_or_key="TEST-2", field="customfield_10000", value=value_dict, notify_users=False
|
|
59
|
+
)
|
|
60
|
+
mock_adapter._client.issue_field_value_append.assert_called_once_with("TEST-2", "customfield_10000", value_dict, notify_users=False)
|
|
61
|
+
assert result["id"] == "10001"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_issue_field_value_append_handles_error(mock_adapter: JiraAdapter) -> None:
|
|
65
|
+
"""Test that issue_field_value_append handles errors from the SDK client."""
|
|
66
|
+
mock_adapter._client.issue_field_value_append.side_effect = Exception("API Error")
|
|
67
|
+
with pytest.raises(JiraAdapterError, match="API Error"):
|
|
68
|
+
mock_adapter.issue_field_value_append(issue_id_or_key="TEST-1", field="customfield_10000", value={"name": "value"})
|
|
69
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
from vds_jira_orchestrator.adapter import JiraAdapter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
9
|
+
def test_get_component_calls_sdk(mock_jira: MagicMock) -> None:
|
|
10
|
+
client = MagicMock()
|
|
11
|
+
client.component.return_value = {"id": 10, "name": "Comp"}
|
|
12
|
+
mock_jira.return_value = client
|
|
13
|
+
|
|
14
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
15
|
+
payload = adapter.get_component(10)
|
|
16
|
+
|
|
17
|
+
client.component.assert_called_once_with(10)
|
|
18
|
+
assert payload["id"] == 10
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
22
|
+
def test_create_component_calls_sdk(mock_jira: MagicMock) -> None:
|
|
23
|
+
client = MagicMock()
|
|
24
|
+
client.create_component.return_value = {"id": 11}
|
|
25
|
+
mock_jira.return_value = client
|
|
26
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
27
|
+
|
|
28
|
+
payload = {"name": "NewComp", "projectKey": "NTTC"}
|
|
29
|
+
result = adapter.create_component(payload)
|
|
30
|
+
|
|
31
|
+
client.create_component.assert_called_once_with(payload)
|
|
32
|
+
assert result["id"] == 11
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
36
|
+
def test_update_component_calls_sdk(mock_jira: MagicMock) -> None:
|
|
37
|
+
client = MagicMock()
|
|
38
|
+
client.update_component.return_value = {"id": 12, "name": "Updated"}
|
|
39
|
+
mock_jira.return_value = client
|
|
40
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
41
|
+
|
|
42
|
+
result = adapter.update_component(12, {"name": "Updated"})
|
|
43
|
+
|
|
44
|
+
client.update_component.assert_called_once_with(12, {"name": "Updated"})
|
|
45
|
+
assert result["name"] == "Updated"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
49
|
+
def test_delete_component_calls_sdk(mock_jira: MagicMock) -> None:
|
|
50
|
+
client = MagicMock()
|
|
51
|
+
mock_jira.return_value = client
|
|
52
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
53
|
+
|
|
54
|
+
adapter.delete_component("13")
|
|
55
|
+
|
|
56
|
+
client.delete_component.assert_called_once_with("13")
|
|
57
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from vds_jira_orchestrator.adapter import JiraAdapter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
10
|
+
def test_get_createmeta_falls_back_to_rest_endpoint(mock_jira: MagicMock) -> None:
|
|
11
|
+
client = MagicMock()
|
|
12
|
+
client.issue_createmeta.side_effect = Exception("boom")
|
|
13
|
+
client.resource_url.return_value = "https://jira.example.com/rest/api/2/issue"
|
|
14
|
+
client.get.return_value = {"projects": []}
|
|
15
|
+
mock_jira.return_value = client
|
|
16
|
+
|
|
17
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
18
|
+
result = adapter.get_createmeta("TEST", issuetype="Task", expand="projects.issuetypes.fields")
|
|
19
|
+
|
|
20
|
+
client.get.assert_called_once_with(
|
|
21
|
+
"https://jira.example.com/rest/api/2/issue/createmeta",
|
|
22
|
+
params={
|
|
23
|
+
"projectKeys": "TEST",
|
|
24
|
+
"issuetypeNames": "Task",
|
|
25
|
+
"expand": "projects.issuetypes.fields",
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
assert result == {"projects": []}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@patch("vds_jira_orchestrator.adapter.Jira")
|
|
32
|
+
def test_get_createmeta_falls_back_when_v2_unavailable(mock_jira: MagicMock) -> None:
|
|
33
|
+
client = MagicMock()
|
|
34
|
+
response = MagicMock()
|
|
35
|
+
response.status_code = 404
|
|
36
|
+
client.issue_createmeta_issuetypes.side_effect = requests.exceptions.HTTPError(response=response)
|
|
37
|
+
client.issue_createmeta_fieldtypes.return_value = {}
|
|
38
|
+
client.issue_createmeta.return_value = {"projects": []}
|
|
39
|
+
mock_jira.return_value = client
|
|
40
|
+
|
|
41
|
+
adapter = JiraAdapter(url="https://jira.example.com", username="user", password="pass")
|
|
42
|
+
result = adapter.get_createmeta("TEST", issuetype="Task", expand="projects.issuetypes.fields")
|
|
43
|
+
|
|
44
|
+
client.issue_createmeta.assert_called_once()
|
|
45
|
+
assert result == {"projects": []}
|