@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,224 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from confluence_orchestrator.cli import app
|
|
8
|
+
from confluence_orchestrator.config import ConfluenceSettings
|
|
9
|
+
from confluence_orchestrator.http import ConfluenceClient
|
|
10
|
+
from typer.testing import CliRunner
|
|
11
|
+
|
|
12
|
+
runner = CliRunner()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(autouse=True)
|
|
16
|
+
def mock_settings(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
17
|
+
def fake_load_settings(strict: bool = False) -> ConfluenceSettings:
|
|
18
|
+
return ConfluenceSettings.model_validate(
|
|
19
|
+
{
|
|
20
|
+
"VDS_USERNAME": "user",
|
|
21
|
+
"VDS_PASSWORD": "pass",
|
|
22
|
+
"INTERNAL_CONFLUENCE_TOKEN": "token-int",
|
|
23
|
+
"EXTERNAL_CONFLUENCE_TOKEN": "token-ext",
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
monkeypatch.setattr("confluence_orchestrator.cli.load_settings", fake_load_settings)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build_fake_client() -> tuple[ConfluenceClient, Mock]:
|
|
31
|
+
inner = Mock()
|
|
32
|
+
client = ConfluenceClient.__new__(ConfluenceClient) # type: ignore[call-arg]
|
|
33
|
+
client._client = inner # type: ignore[attr-defined]
|
|
34
|
+
client._log = Mock()
|
|
35
|
+
client._is_cloud = False # type: ignore[attr-defined]
|
|
36
|
+
client._with_retry = lambda operation, _: operation() # type: ignore[assignment]
|
|
37
|
+
return client, inner
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Client method coverage
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_client_get_page_ancestors_invokes_sdk() -> None:
|
|
46
|
+
client, inner = _build_fake_client()
|
|
47
|
+
inner.get_page_ancestors.return_value = [{"id": "1", "title": "Parent 1"}, {"id": "2", "title": "Parent 2"}]
|
|
48
|
+
|
|
49
|
+
result = client.get_page_ancestors("123")
|
|
50
|
+
|
|
51
|
+
assert len(result) == 2
|
|
52
|
+
assert result[0]["title"] == "Parent 1"
|
|
53
|
+
inner.get_page_ancestors.assert_called_once_with("123")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_client_move_page_invokes_sdk() -> None:
|
|
57
|
+
client, inner = _build_fake_client()
|
|
58
|
+
inner.move_page.return_value = {"id": "123", "title": "Moved Page"}
|
|
59
|
+
|
|
60
|
+
result = client.move_page("123", "Target Page", position="append")
|
|
61
|
+
|
|
62
|
+
assert result["id"] == "123"
|
|
63
|
+
inner.move_page.assert_called_once_with("123", "Target Page", position="append")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_client_move_page_with_position() -> None:
|
|
67
|
+
client, inner = _build_fake_client()
|
|
68
|
+
inner.move_page.return_value = {"id": "123"}
|
|
69
|
+
|
|
70
|
+
client.move_page("123", "Target Page", position="before")
|
|
71
|
+
|
|
72
|
+
inner.move_page.assert_called_once_with("123", "Target Page", position="before")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_client_get_tables_from_page_invokes_sdk() -> None:
|
|
76
|
+
client, inner = _build_fake_client()
|
|
77
|
+
inner.get_tables_from_page.return_value = [{"rows": 2, "cols": 3}, {"rows": 1, "cols": 2}]
|
|
78
|
+
|
|
79
|
+
result = client.get_tables_from_page("123")
|
|
80
|
+
|
|
81
|
+
assert len(result) == 2
|
|
82
|
+
inner.get_tables_from_page.assert_called_once_with("123")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_client_scrap_regex_from_page_invokes_sdk() -> None:
|
|
86
|
+
client, inner = _build_fake_client()
|
|
87
|
+
inner.scrap_regex_from_page.return_value = [{"match": "test1"}, {"match": "test2"}]
|
|
88
|
+
|
|
89
|
+
result = client.scrap_regex_from_page("123", r"\d+")
|
|
90
|
+
|
|
91
|
+
assert len(result) == 2
|
|
92
|
+
inner.scrap_regex_from_page.assert_called_once_with("123", r"\d+")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_client_get_all_restrictions_for_content_invokes_sdk() -> None:
|
|
96
|
+
client, inner = _build_fake_client()
|
|
97
|
+
inner.get_all_restrictions_for_content.return_value = {"read": ["user1"], "write": ["user2"]}
|
|
98
|
+
|
|
99
|
+
result = client.get_all_restrictions_for_content("123")
|
|
100
|
+
|
|
101
|
+
assert "read" in result
|
|
102
|
+
inner.get_all_restrictions_for_content.assert_called_once_with("123")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
# CLI command coverage
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class DummyAdvancedContentClient:
|
|
111
|
+
def __init__(self) -> None:
|
|
112
|
+
self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
|
|
113
|
+
|
|
114
|
+
def get_page_ancestors(self, page_id: str) -> list[dict[str, Any]]:
|
|
115
|
+
self.calls.append(("get_page_ancestors", (page_id,), {}))
|
|
116
|
+
return [{"id": "1", "title": "Parent"}]
|
|
117
|
+
|
|
118
|
+
def move_page(self, page_id: str, target_title: str, position: str = "append") -> dict[str, Any]:
|
|
119
|
+
self.calls.append(("move_page", (page_id, target_title), {"position": position}))
|
|
120
|
+
return {"id": page_id, "moved": True}
|
|
121
|
+
|
|
122
|
+
def get_tables_from_page(self, page_id: str) -> list[dict[str, Any]]:
|
|
123
|
+
self.calls.append(("get_tables_from_page", (page_id,), {}))
|
|
124
|
+
return [{"rows": 2, "cols": 3}]
|
|
125
|
+
|
|
126
|
+
def scrap_regex_from_page(self, page_id: str, regex: str) -> list[dict[str, Any]]:
|
|
127
|
+
self.calls.append(("scrap_regex_from_page", (page_id, regex), {}))
|
|
128
|
+
return [{"match": "test"}]
|
|
129
|
+
|
|
130
|
+
def get_all_restrictions_for_content(self, content_id: str) -> dict[str, Any]:
|
|
131
|
+
self.calls.append(("get_all_restrictions_for_content", (content_id,), {}))
|
|
132
|
+
return {"read": ["user1"]}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_cli_content_ancestors_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
136
|
+
client = DummyAdvancedContentClient()
|
|
137
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
138
|
+
|
|
139
|
+
result = runner.invoke(app, ["content", "ancestors", "123"])
|
|
140
|
+
|
|
141
|
+
assert result.exit_code == 0
|
|
142
|
+
assert "123" in result.stdout
|
|
143
|
+
assert len(client.calls) == 1
|
|
144
|
+
assert client.calls[0][0] == "get_page_ancestors"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_cli_content_move_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
148
|
+
client = DummyAdvancedContentClient()
|
|
149
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
150
|
+
|
|
151
|
+
result = runner.invoke(app, ["content", "move", "123", "--target-title", "Target Page", "--position", "append", "--yes"])
|
|
152
|
+
|
|
153
|
+
assert result.exit_code == 0
|
|
154
|
+
assert "123" in result.stdout
|
|
155
|
+
assert len(client.calls) == 1
|
|
156
|
+
assert client.calls[0][0] == "move_page"
|
|
157
|
+
assert client.calls[0][2]["position"] == "append"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_cli_content_move_requires_yes(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
161
|
+
client = DummyAdvancedContentClient()
|
|
162
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
163
|
+
|
|
164
|
+
result = runner.invoke(app, ["content", "move", "123", "--target-title", "Target Page"])
|
|
165
|
+
|
|
166
|
+
assert result.exit_code != 0
|
|
167
|
+
assert "--yes required" in result.stdout or "--yes required" in result.stderr
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_cli_content_move_validates_position(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
171
|
+
client = DummyAdvancedContentClient()
|
|
172
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
173
|
+
|
|
174
|
+
result = runner.invoke(app, ["content", "move", "123", "--target-title", "Target", "--position", "invalid", "--yes"])
|
|
175
|
+
|
|
176
|
+
assert result.exit_code != 0
|
|
177
|
+
assert "must be one of" in result.stdout or "must be one of" in result.stderr
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_cli_content_tables_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
181
|
+
client = DummyAdvancedContentClient()
|
|
182
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
183
|
+
|
|
184
|
+
result = runner.invoke(app, ["content", "tables", "123"])
|
|
185
|
+
|
|
186
|
+
assert result.exit_code == 0
|
|
187
|
+
assert "123" in result.stdout
|
|
188
|
+
assert len(client.calls) == 1
|
|
189
|
+
assert client.calls[0][0] == "get_tables_from_page"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_cli_content_regex_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
193
|
+
client = DummyAdvancedContentClient()
|
|
194
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
195
|
+
|
|
196
|
+
result = runner.invoke(app, ["content", "regex", "123", "--pattern", r"\d+"])
|
|
197
|
+
|
|
198
|
+
assert result.exit_code == 0
|
|
199
|
+
assert "123" in result.stdout
|
|
200
|
+
assert len(client.calls) == 1
|
|
201
|
+
assert client.calls[0][0] == "scrap_regex_from_page"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_cli_content_regex_requires_pattern(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
205
|
+
client = DummyAdvancedContentClient()
|
|
206
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
207
|
+
|
|
208
|
+
result = runner.invoke(app, ["content", "regex", "123"])
|
|
209
|
+
|
|
210
|
+
assert result.exit_code != 0
|
|
211
|
+
# Typer will show error about missing required option
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_cli_content_restrictions_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
215
|
+
client = DummyAdvancedContentClient()
|
|
216
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
217
|
+
|
|
218
|
+
result = runner.invoke(app, ["content", "restrictions", "123"])
|
|
219
|
+
|
|
220
|
+
assert result.exit_code == 0
|
|
221
|
+
assert "123" in result.stdout
|
|
222
|
+
assert len(client.calls) == 1
|
|
223
|
+
assert client.calls[0][0] == "get_all_restrictions_for_content"
|
|
224
|
+
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Tests for Confluence advanced CQL search."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from confluence_orchestrator.cli import app
|
|
9
|
+
from confluence_orchestrator.config import ConfluenceSettings
|
|
10
|
+
from confluence_orchestrator.errors import ConfluenceClientError
|
|
11
|
+
from confluence_orchestrator.http import ConfluenceClient
|
|
12
|
+
from typer.testing import CliRunner
|
|
13
|
+
|
|
14
|
+
runner = CliRunner()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def mock_settings() -> ConfluenceSettings:
|
|
19
|
+
"""Mock ConfluenceSettings."""
|
|
20
|
+
return ConfluenceSettings.model_validate(
|
|
21
|
+
{
|
|
22
|
+
"VDS_USERNAME": "test_user",
|
|
23
|
+
"VDS_PASSWORD": "test_pass",
|
|
24
|
+
"INTERNAL_CONFLUENCE_TOKEN": "token-int",
|
|
25
|
+
"EXTERNAL_CONFLUENCE_TOKEN": "token-ext",
|
|
26
|
+
"INTERNAL_CONFLUENCE_URL": "http://internal.confluence.com",
|
|
27
|
+
"EXTERNAL_CONFLUENCE_URL": "http://external.confluence.com",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def mock_sdk_client() -> MagicMock:
|
|
34
|
+
"""Mock the underlying AtlassianConfluenceServer client."""
|
|
35
|
+
with patch("confluence_orchestrator.http.AtlassianConfluenceServer") as mock:
|
|
36
|
+
yield mock.return_value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def server_client(mock_settings: ConfluenceSettings, mock_sdk_client: MagicMock) -> ConfluenceClient:
|
|
41
|
+
"""Return a ConfluenceClient configured for server mode."""
|
|
42
|
+
client = ConfluenceClient(mock_settings, server="internal")
|
|
43
|
+
client._client = mock_sdk_client
|
|
44
|
+
client._is_cloud = False
|
|
45
|
+
client._with_retry = lambda operation, _: operation()
|
|
46
|
+
return client
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestCqlAdvanced:
|
|
50
|
+
"""Tests for cql_advanced method."""
|
|
51
|
+
|
|
52
|
+
def test_cql_advanced_success(self, server_client: ConfluenceClient, mock_sdk_client: MagicMock) -> None:
|
|
53
|
+
"""Test successful advanced CQL search."""
|
|
54
|
+
mock_sdk_client.cql.return_value = {"results": [{"id": "123", "title": "Test Page"}], "size": 1}
|
|
55
|
+
|
|
56
|
+
result = server_client.cql_advanced("type = page", limit=50, start=10, excerpt="highlighted")
|
|
57
|
+
|
|
58
|
+
assert result["size"] == 1
|
|
59
|
+
mock_sdk_client.cql.assert_called_once()
|
|
60
|
+
call_kwargs = mock_sdk_client.cql.call_args[1]
|
|
61
|
+
assert call_kwargs["limit"] == 50
|
|
62
|
+
assert call_kwargs["start"] == 10
|
|
63
|
+
assert call_kwargs["excerpt"] == "highlighted"
|
|
64
|
+
|
|
65
|
+
def test_cql_advanced_with_expand(self, server_client: ConfluenceClient, mock_sdk_client: MagicMock) -> None:
|
|
66
|
+
"""Test advanced CQL search with expand option."""
|
|
67
|
+
mock_sdk_client.cql.return_value = {"results": [], "size": 0}
|
|
68
|
+
|
|
69
|
+
server_client.cql_advanced("type = page", expand=["body.storage", "space"])
|
|
70
|
+
|
|
71
|
+
call_kwargs = mock_sdk_client.cql.call_args[1]
|
|
72
|
+
assert call_kwargs["expand"] == "body.storage,space"
|
|
73
|
+
|
|
74
|
+
def test_cql_advanced_defaults(self, server_client: ConfluenceClient, mock_sdk_client: MagicMock) -> None:
|
|
75
|
+
"""Test advanced CQL search with default parameters."""
|
|
76
|
+
mock_sdk_client.cql.return_value = {"results": [], "size": 0}
|
|
77
|
+
|
|
78
|
+
server_client.cql_advanced("type = page")
|
|
79
|
+
|
|
80
|
+
call_kwargs = mock_sdk_client.cql.call_args[1]
|
|
81
|
+
assert call_kwargs["limit"] == 25
|
|
82
|
+
assert call_kwargs["start"] == 0
|
|
83
|
+
assert "excerpt" not in call_kwargs or call_kwargs.get("excerpt") is None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class TestSearchBySpaceAndType:
|
|
87
|
+
"""Tests for search_by_space_and_type method."""
|
|
88
|
+
|
|
89
|
+
def test_search_by_space_and_type_space_only(self, server_client: ConfluenceClient, mock_sdk_client: MagicMock) -> None:
|
|
90
|
+
"""Test search by space only."""
|
|
91
|
+
mock_sdk_client.cql.return_value = {"results": [], "size": 0}
|
|
92
|
+
|
|
93
|
+
server_client.search_by_space_and_type(space_key="TEST")
|
|
94
|
+
|
|
95
|
+
call_args = mock_sdk_client.cql.call_args[0]
|
|
96
|
+
assert 'space = "TEST"' in call_args[0]
|
|
97
|
+
|
|
98
|
+
def test_search_by_space_and_type_type_only(self, server_client: ConfluenceClient, mock_sdk_client: MagicMock) -> None:
|
|
99
|
+
"""Test search by content type only."""
|
|
100
|
+
mock_sdk_client.cql.return_value = {"results": [], "size": 0}
|
|
101
|
+
|
|
102
|
+
server_client.search_by_space_and_type(content_type="page")
|
|
103
|
+
|
|
104
|
+
call_args = mock_sdk_client.cql.call_args[0]
|
|
105
|
+
assert 'type = "page"' in call_args[0]
|
|
106
|
+
|
|
107
|
+
def test_search_by_space_and_type_both(self, server_client: ConfluenceClient, mock_sdk_client: MagicMock) -> None:
|
|
108
|
+
"""Test search by both space and type."""
|
|
109
|
+
mock_sdk_client.cql.return_value = {"results": [], "size": 0}
|
|
110
|
+
|
|
111
|
+
server_client.search_by_space_and_type(space_key="TEST", content_type="page")
|
|
112
|
+
|
|
113
|
+
call_args = mock_sdk_client.cql.call_args[0]
|
|
114
|
+
assert 'space = "TEST"' in call_args[0]
|
|
115
|
+
assert 'type = "page"' in call_args[0]
|
|
116
|
+
assert " AND " in call_args[0]
|
|
117
|
+
|
|
118
|
+
def test_search_by_space_and_type_requires_at_least_one(self, server_client: ConfluenceClient) -> None:
|
|
119
|
+
"""Test that search_by_space_and_type requires at least one parameter."""
|
|
120
|
+
with pytest.raises(ConfluenceClientError, match="At least one of space_key or content_type"):
|
|
121
|
+
server_client.search_by_space_and_type()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# CLI command tests
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DummySearchClient:
|
|
130
|
+
def __init__(self, cloud: bool = False) -> None:
|
|
131
|
+
self.calls: list = []
|
|
132
|
+
self._is_cloud = cloud # type: ignore[attr-defined]
|
|
133
|
+
|
|
134
|
+
def search_cql(self, cql: str, **kwargs: any) -> dict:
|
|
135
|
+
self.calls.append(("search_cql", cql, kwargs))
|
|
136
|
+
return {"results": [], "size": 0}
|
|
137
|
+
|
|
138
|
+
def cql_advanced(self, cql: str, **kwargs: any) -> dict:
|
|
139
|
+
self.calls.append(("cql_advanced", cql, kwargs))
|
|
140
|
+
return {"results": [], "size": 0}
|
|
141
|
+
|
|
142
|
+
def search_by_space_and_type(self, **kwargs: any) -> dict:
|
|
143
|
+
self.calls.append(("search_by_space_and_type", kwargs))
|
|
144
|
+
return {"results": [], "size": 0}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_cli_search_advanced_flag(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
148
|
+
"""Test search command with --advanced flag."""
|
|
149
|
+
client = DummySearchClient(cloud=False)
|
|
150
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_content_client", lambda *args: client)
|
|
151
|
+
|
|
152
|
+
result = runner.invoke(app, ["search", "--cql", "type = page", "--advanced", "--excerpt", "highlighted"])
|
|
153
|
+
|
|
154
|
+
assert result.exit_code == 0
|
|
155
|
+
assert len(client.calls) == 1
|
|
156
|
+
assert client.calls[0][0] == "cql_advanced"
|
|
157
|
+
assert client.calls[0][2].get("excerpt") == "highlighted"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_cli_search_by_space_type_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
161
|
+
"""Test search-by-space-type command success."""
|
|
162
|
+
client = DummySearchClient(cloud=False)
|
|
163
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_content_client", lambda *args: client)
|
|
164
|
+
|
|
165
|
+
result = runner.invoke(app, ["search-by-space-type", "--space", "TEST", "--type", "page"])
|
|
166
|
+
|
|
167
|
+
assert result.exit_code == 0
|
|
168
|
+
assert len(client.calls) == 1
|
|
169
|
+
assert client.calls[0][0] == "search_by_space_and_type"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_cli_search_by_space_type_requires_parameter(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
173
|
+
"""Test search-by-space-type command requires at least one parameter."""
|
|
174
|
+
client = DummySearchClient(cloud=False)
|
|
175
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_content_client", lambda *args: client)
|
|
176
|
+
|
|
177
|
+
result = runner.invoke(app, ["search-by-space-type"])
|
|
178
|
+
|
|
179
|
+
assert result.exit_code != 0
|
|
180
|
+
assert (
|
|
181
|
+
"--space" in result.stdout
|
|
182
|
+
or "--type" in result.stdout
|
|
183
|
+
or "At least one" in result.stdout
|
|
184
|
+
or "--space" in result.stderr
|
|
185
|
+
or "--type" in result.stderr
|
|
186
|
+
or "At least one" in result.stderr
|
|
187
|
+
)
|
|
188
|
+
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Tests for Confluence cache management (Server-only)."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from confluence_orchestrator.cli import app
|
|
8
|
+
from confluence_orchestrator.errors import ConfluenceClientError
|
|
9
|
+
from confluence_orchestrator.http import ConfluenceClient
|
|
10
|
+
from typer.testing import CliRunner
|
|
11
|
+
|
|
12
|
+
runner = CliRunner()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def mock_settings():
|
|
17
|
+
"""Mock Confluence settings."""
|
|
18
|
+
settings = MagicMock()
|
|
19
|
+
settings.url_for.return_value = "http://confluence.digital.vn"
|
|
20
|
+
settings.token_for.return_value = "test-token"
|
|
21
|
+
settings.username = None
|
|
22
|
+
settings.password = None
|
|
23
|
+
settings.max_retries = 3
|
|
24
|
+
settings.retry_backoff_factor = 0.5
|
|
25
|
+
settings.default_server = "internal"
|
|
26
|
+
settings.internal_token = "test-token"
|
|
27
|
+
settings.external_token = None
|
|
28
|
+
return settings
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def mock_sdk_client():
|
|
33
|
+
"""Mock SDK client."""
|
|
34
|
+
client = MagicMock()
|
|
35
|
+
client.clean_all_caches.return_value = None
|
|
36
|
+
client.clean_package_cache.return_value = None
|
|
37
|
+
client.request.return_value = MagicMock(json=lambda: {"stats": "data"})
|
|
38
|
+
return client
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def server_client(mock_settings, mock_sdk_client):
|
|
43
|
+
"""Create ConfluenceClient in Server mode."""
|
|
44
|
+
with patch("confluence_orchestrator.http.AtlassianConfluenceServer", return_value=mock_sdk_client):
|
|
45
|
+
client = ConfluenceClient(mock_settings, server="internal")
|
|
46
|
+
client._is_cloud = False
|
|
47
|
+
return client
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def cloud_client(mock_settings, mock_sdk_client):
|
|
52
|
+
"""Create ConfluenceClient in Cloud mode."""
|
|
53
|
+
with patch("confluence_orchestrator.http.AtlassianConfluenceCloud", return_value=mock_sdk_client):
|
|
54
|
+
client = ConfluenceClient(mock_settings, server="internal")
|
|
55
|
+
client._is_cloud = True
|
|
56
|
+
return client
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestGetCacheStatistics:
|
|
60
|
+
"""Tests for get_cache_statistics method."""
|
|
61
|
+
|
|
62
|
+
def test_get_cache_statistics_success(self, server_client, mock_sdk_client):
|
|
63
|
+
"""Test successful cache statistics retrieval."""
|
|
64
|
+
mock_response = MagicMock()
|
|
65
|
+
mock_response.json.return_value = {"cache_stats": {"hits": 100, "misses": 50}}
|
|
66
|
+
mock_sdk_client.request.return_value = mock_response
|
|
67
|
+
|
|
68
|
+
result = server_client.get_cache_statistics()
|
|
69
|
+
|
|
70
|
+
assert result == {"cache_stats": {"hits": 100, "misses": 50}}
|
|
71
|
+
mock_sdk_client.request.assert_called_once_with(method="GET", path="/rest/cache/1.0/stats")
|
|
72
|
+
|
|
73
|
+
def test_get_cache_statistics_cloud_mode_error(self, cloud_client):
|
|
74
|
+
"""Test that get_cache_statistics raises error in Cloud mode."""
|
|
75
|
+
with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
|
|
76
|
+
cloud_client.get_cache_statistics()
|
|
77
|
+
|
|
78
|
+
def test_get_cache_statistics_handles_response_dict(self, server_client, mock_sdk_client):
|
|
79
|
+
"""Test handling when request returns dict directly."""
|
|
80
|
+
mock_sdk_client.request.return_value = {"cache_stats": {"hits": 100}}
|
|
81
|
+
|
|
82
|
+
result = server_client.get_cache_statistics()
|
|
83
|
+
|
|
84
|
+
assert result == {"cache_stats": {"hits": 100}}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TestFlushCache:
|
|
88
|
+
"""Tests for flush_cache method."""
|
|
89
|
+
|
|
90
|
+
def test_flush_cache_all_success(self, server_client, mock_sdk_client):
|
|
91
|
+
"""Test successful flush of all caches."""
|
|
92
|
+
server_client.flush_cache()
|
|
93
|
+
|
|
94
|
+
mock_sdk_client.clean_all_caches.assert_called_once()
|
|
95
|
+
|
|
96
|
+
def test_flush_cache_specific_success(self, server_client, mock_sdk_client):
|
|
97
|
+
"""Test successful flush of specific cache."""
|
|
98
|
+
server_client.flush_cache(cache_name="com.gliffy.cache.gon")
|
|
99
|
+
|
|
100
|
+
mock_sdk_client.clean_package_cache.assert_called_once_with(cache_name="com.gliffy.cache.gon")
|
|
101
|
+
|
|
102
|
+
def test_flush_cache_cloud_mode_error(self, cloud_client):
|
|
103
|
+
"""Test that flush_cache raises error in Cloud mode."""
|
|
104
|
+
with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
|
|
105
|
+
cloud_client.flush_cache()
|
|
106
|
+
|
|
107
|
+
def test_flush_cache_specific_cloud_mode_error(self, cloud_client):
|
|
108
|
+
"""Test that flush_cache with cache_name raises error in Cloud mode."""
|
|
109
|
+
with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
|
|
110
|
+
cloud_client.flush_cache(cache_name="test-cache")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestGetCacheSize:
|
|
114
|
+
"""Tests for get_cache_size method."""
|
|
115
|
+
|
|
116
|
+
def test_get_cache_size_success(self, server_client, mock_sdk_client):
|
|
117
|
+
"""Test successful cache size retrieval."""
|
|
118
|
+
mock_response = MagicMock()
|
|
119
|
+
mock_response.json.return_value = {"total_size": "100MB", "cache_count": 10}
|
|
120
|
+
mock_sdk_client.request.return_value = mock_response
|
|
121
|
+
|
|
122
|
+
result = server_client.get_cache_size()
|
|
123
|
+
|
|
124
|
+
assert result == {"total_size": "100MB", "cache_count": 10}
|
|
125
|
+
mock_sdk_client.request.assert_called_once_with(method="GET", path="/rest/cache/1.0/size")
|
|
126
|
+
|
|
127
|
+
def test_get_cache_size_cloud_mode_error(self, cloud_client):
|
|
128
|
+
"""Test that get_cache_size raises error in Cloud mode."""
|
|
129
|
+
with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
|
|
130
|
+
cloud_client.get_cache_size()
|
|
131
|
+
|
|
132
|
+
def test_get_cache_size_handles_response_dict(self, server_client, mock_sdk_client):
|
|
133
|
+
"""Test handling when request returns dict directly."""
|
|
134
|
+
mock_sdk_client.request.return_value = {"total_size": "50MB"}
|
|
135
|
+
|
|
136
|
+
result = server_client.get_cache_size()
|
|
137
|
+
|
|
138
|
+
assert result == {"total_size": "50MB"}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ---------------------------------------------------------------------------
|
|
142
|
+
# CLI command coverage
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class DummyCacheClient:
|
|
147
|
+
def __init__(self, cloud: bool = False) -> None:
|
|
148
|
+
self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
|
|
149
|
+
self._is_cloud = cloud # type: ignore[attr-defined]
|
|
150
|
+
|
|
151
|
+
def get_cache_statistics(self) -> dict[str, Any]:
|
|
152
|
+
if self._is_cloud:
|
|
153
|
+
raise ConfluenceClientError("Server-only feature")
|
|
154
|
+
self.calls.append(("get_cache_statistics", (), {}))
|
|
155
|
+
return {"cache_stats": {"hits": 100, "misses": 50}}
|
|
156
|
+
|
|
157
|
+
def flush_cache(self, cache_name: str | None = None) -> None:
|
|
158
|
+
if self._is_cloud:
|
|
159
|
+
raise ConfluenceClientError("Server-only feature")
|
|
160
|
+
self.calls.append(("flush_cache", (cache_name,), {}))
|
|
161
|
+
|
|
162
|
+
def get_cache_size(self) -> dict[str, Any]:
|
|
163
|
+
if self._is_cloud:
|
|
164
|
+
raise ConfluenceClientError("Server-only feature")
|
|
165
|
+
self.calls.append(("get_cache_size", (), {}))
|
|
166
|
+
return {"total_size": "100MB", "cache_count": 10}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_cli_cache_statistics_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
170
|
+
client = DummyCacheClient(cloud=False)
|
|
171
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
172
|
+
|
|
173
|
+
result = runner.invoke(app, ["cache", "statistics"])
|
|
174
|
+
|
|
175
|
+
assert result.exit_code == 0
|
|
176
|
+
assert "cache_statistics" in result.stdout
|
|
177
|
+
assert len(client.calls) == 1
|
|
178
|
+
assert client.calls[0][0] == "get_cache_statistics"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_cli_cache_flush_all_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
182
|
+
client = DummyCacheClient(cloud=False)
|
|
183
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
184
|
+
|
|
185
|
+
result = runner.invoke(app, ["cache", "flush", "--yes"])
|
|
186
|
+
|
|
187
|
+
assert result.exit_code == 0
|
|
188
|
+
assert "success" in result.stdout
|
|
189
|
+
assert len(client.calls) == 1
|
|
190
|
+
assert client.calls[0][0] == "flush_cache"
|
|
191
|
+
assert client.calls[0][1][0] is None # cache_name is None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def test_cli_cache_flush_specific_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
195
|
+
client = DummyCacheClient(cloud=False)
|
|
196
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
197
|
+
|
|
198
|
+
result = runner.invoke(app, ["cache", "flush", "--cache-name", "com.gliffy.cache", "--yes"])
|
|
199
|
+
|
|
200
|
+
assert result.exit_code == 0
|
|
201
|
+
assert "success" in result.stdout
|
|
202
|
+
assert len(client.calls) == 1
|
|
203
|
+
assert client.calls[0][0] == "flush_cache"
|
|
204
|
+
assert client.calls[0][1][0] == "com.gliffy.cache"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_cli_cache_flush_requires_yes(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
208
|
+
client = DummyCacheClient(cloud=False)
|
|
209
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
210
|
+
|
|
211
|
+
result = runner.invoke(app, ["cache", "flush"])
|
|
212
|
+
|
|
213
|
+
assert result.exit_code != 0
|
|
214
|
+
assert "--yes" in result.stdout or "--yes" in result.stderr or "without --yes" in result.stdout
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_cli_cache_size_success(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
218
|
+
client = DummyCacheClient(cloud=False)
|
|
219
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
220
|
+
|
|
221
|
+
result = runner.invoke(app, ["cache", "size"])
|
|
222
|
+
|
|
223
|
+
assert result.exit_code == 0
|
|
224
|
+
assert "cache_size" in result.stdout
|
|
225
|
+
assert len(client.calls) == 1
|
|
226
|
+
assert client.calls[0][0] == "get_cache_size"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_cli_cache_cloud_mode_error(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
230
|
+
client = DummyCacheClient(cloud=True)
|
|
231
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
232
|
+
|
|
233
|
+
result = runner.invoke(app, ["cache", "statistics"])
|
|
234
|
+
|
|
235
|
+
assert result.exit_code != 0
|
|
236
|
+
assert "Server-only" in result.stderr or "Server-only" in result.stdout
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def test_cli_cache_invalid_action(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
240
|
+
client = DummyCacheClient(cloud=False)
|
|
241
|
+
monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
|
|
242
|
+
|
|
243
|
+
result = runner.invoke(app, ["cache", "invalid"])
|
|
244
|
+
|
|
245
|
+
assert result.exit_code != 0
|
|
246
|
+
assert "Unknown action" in result.stdout or "Unknown action" in result.stderr
|
|
247
|
+
|