@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.
Files changed (303) hide show
  1. package/package.json +2 -2
  2. package/src/global.js +70 -10
  3. package/tools/skills/vds-scripts-skill/.openskills.json +6 -0
  4. package/tools/skills/vds-scripts-skill/QUALITY.md +44 -0
  5. package/tools/skills/vds-scripts-skill/SKILL.md +135 -0
  6. package/tools/skills/vds-scripts-skill/references/audit-commands.md +171 -0
  7. package/tools/skills/vds-scripts-skill/references/capability-index.md +34 -0
  8. package/tools/skills/vds-scripts-skill/references/development-commands.md +12 -0
  9. package/tools/skills/vds-scripts-skill/references/google-sheets.md +73 -0
  10. package/tools/skills/vds-scripts-skill/references/integration-commands.md +17 -0
  11. package/tools/skills/vds-scripts-skill/references/platform-bootstrap.md +31 -0
  12. package/tools/skills/vds-scripts-skill/references/specialist-routing.md +14 -0
  13. package/tools/skills/vds-scripts-skill/references/validation-commands.md +15 -0
  14. package/tools/skills/vsaf-build/SKILL.md +32 -2
  15. package/tools/skills/vsaf-ship/SKILL.md +41 -10
  16. package/tools/skills/vsaf-test/SKILL.md +8 -0
  17. package/tools/vds-scripts/.mcp.json +11 -0
  18. package/tools/vds-scripts/.secrets.baseline +133 -0
  19. package/tools/vds-scripts/AGENTS.md +152 -0
  20. package/tools/vds-scripts/CLAUDE.md +101 -0
  21. package/tools/vds-scripts/CLI_COMMAND_OPTIMIZATION.md +156 -0
  22. package/tools/vds-scripts/PACKAGE_P125B_IMPLEMENTATION_SUMMARY.md +131 -0
  23. package/tools/vds-scripts/PROJECT_COMPLETION_SUMMARY.md +45 -0
  24. package/tools/vds-scripts/README.md +97 -0
  25. package/tools/vds-scripts/bitbucket_manifest_mapping.toml +34 -0
  26. package/tools/vds-scripts/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
  27. package/tools/vds-scripts/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
  28. package/tools/vds-scripts/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
  29. package/tools/vds-scripts/bitbucket_orchestrator/FEATURES.md +908 -0
  30. package/tools/vds-scripts/bitbucket_orchestrator/README.md +687 -0
  31. package/tools/vds-scripts/bitbucket_orchestrator/pyproject.toml +40 -0
  32. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +20 -0
  33. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +657 -0
  34. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2108 -0
  35. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2534 -0
  36. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +171 -0
  37. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +67 -0
  38. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
  39. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
  40. package/tools/vds-scripts/bitbucket_orchestrator/tests/__init__.py +8 -0
  41. package/tools/vds-scripts/bitbucket_orchestrator/tests/conftest.py +65 -0
  42. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_advanced_search.py +151 -0
  43. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_async_client.py +546 -0
  44. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_branch_permissions.py +145 -0
  45. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_cli.py +115 -0
  46. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client.py +157 -0
  47. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_branch_conditions.py +79 -0
  48. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_advanced.py +163 -0
  49. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_file.py +32 -0
  50. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_deployment_environments.py +194 -0
  51. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_issues.py +164 -0
  52. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +179 -0
  53. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pr_blockers.py +119 -0
  54. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_repository_variables.py +156 -0
  55. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code.py +98 -0
  56. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_advanced.py +282 -0
  57. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_insights.py +335 -0
  58. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_conditions.py +147 -0
  59. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_config.py +131 -0
  60. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
  61. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_factory.py +371 -0
  62. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_fork_operations.py +204 -0
  63. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_issue_cli.py +261 -0
  64. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pipeline_advanced.py +270 -0
  65. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pr_blocker.py +204 -0
  66. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_protocols.py +334 -0
  67. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_settings.py +343 -0
  68. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_variables.py +270 -0
  69. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_webhooks.py +189 -0
  70. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_workspace.py +233 -0
  71. package/tools/vds-scripts/bitbucket_orchestrator/uv.lock +742 -0
  72. package/tools/vds-scripts/confluence_orchestrator/Dockerfile +19 -0
  73. package/tools/vds-scripts/confluence_orchestrator/README.md +412 -0
  74. package/tools/vds-scripts/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
  75. package/tools/vds-scripts/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
  76. package/tools/vds-scripts/confluence_orchestrator/pyproject.toml +48 -0
  77. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/__init__.py +20 -0
  78. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/cli.py +2532 -0
  79. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/config.py +175 -0
  80. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content.py +290 -0
  81. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +94 -0
  82. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1835 -0
  83. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/errors.py +80 -0
  84. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/eventing.py +109 -0
  85. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/http.py +1114 -0
  86. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +165 -0
  87. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
  88. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree.py +121 -0
  89. package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_from_markdown.py +213 -0
  90. package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_to_confluence.py +305 -0
  91. package/tools/vds-scripts/confluence_orchestrator/sync_png_attachments.py +305 -0
  92. package/tools/vds-scripts/confluence_orchestrator/tests/__init__.py +0 -0
  93. package/tools/vds-scripts/confluence_orchestrator/tests/conftest.py +8 -0
  94. package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_content.py +224 -0
  95. package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_search.py +188 -0
  96. package/tools/vds-scripts/confluence_orchestrator/tests/test_cache_management.py +247 -0
  97. package/tools/vds-scripts/confluence_orchestrator/tests/test_cli.py +499 -0
  98. package/tools/vds-scripts/confluence_orchestrator/tests/test_config.py +83 -0
  99. package/tools/vds-scripts/confluence_orchestrator/tests/test_content.py +186 -0
  100. package/tools/vds-scripts/confluence_orchestrator/tests/test_content_flags.py +27 -0
  101. package/tools/vds-scripts/confluence_orchestrator/tests/test_crawl_tree.py +2250 -0
  102. package/tools/vds-scripts/confluence_orchestrator/tests/test_draft_management.py +223 -0
  103. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing.py +71 -0
  104. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
  105. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
  106. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
  107. package/tools/vds-scripts/confluence_orchestrator/tests/test_export.py +230 -0
  108. package/tools/vds-scripts/confluence_orchestrator/tests/test_history.py +204 -0
  109. package/tools/vds-scripts/confluence_orchestrator/tests/test_http.py +117 -0
  110. package/tools/vds-scripts/confluence_orchestrator/tests/test_orchestration.py +91 -0
  111. package/tools/vds-scripts/confluence_orchestrator/tests/test_reporting.py +24 -0
  112. package/tools/vds-scripts/confluence_orchestrator/tests/test_search_cql.py +34 -0
  113. package/tools/vds-scripts/confluence_orchestrator/tests/test_space_management.py +237 -0
  114. package/tools/vds-scripts/confluence_orchestrator/tests/test_space_permissions.py +332 -0
  115. package/tools/vds-scripts/confluence_orchestrator/tests/test_user_group_management.py +388 -0
  116. package/tools/vds-scripts/confluence_orchestrator/uv.lock +1023 -0
  117. package/tools/vds-scripts/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
  118. package/tools/vds-scripts/git_orchestrator/README.md +280 -0
  119. package/tools/vds-scripts/git_orchestrator/VERIFICATION_REPORT.md +152 -0
  120. package/tools/vds-scripts/git_orchestrator/pyproject.toml +35 -0
  121. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__init__.py +7 -0
  122. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
  123. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/cli.py +847 -0
  124. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
  125. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/manifest.py +129 -0
  126. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +819 -0
  127. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/reporting.py +53 -0
  128. package/tools/vds-scripts/git_orchestrator/tests/__init__.py +0 -0
  129. package/tools/vds-scripts/git_orchestrator/tests/test_cli_settings.py +21 -0
  130. package/tools/vds-scripts/git_orchestrator/tests/test_integration.py +74 -0
  131. package/tools/vds-scripts/git_orchestrator/tests/test_manifest.py +79 -0
  132. package/tools/vds-scripts/git_orchestrator/tests/test_orchestrator.py +204 -0
  133. package/tools/vds-scripts/git_orchestrator/tests/test_public_api.py +236 -0
  134. package/tools/vds-scripts/git_orchestrator/tests/test_resilience.py +345 -0
  135. package/tools/vds-scripts/git_orchestrator/uv.lock +271 -0
  136. package/tools/vds-scripts/jira_orchestrator/README.md +770 -0
  137. package/tools/vds-scripts/jira_orchestrator/pyproject.toml +39 -0
  138. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +1 -0
  139. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1320 -0
  140. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2271 -0
  141. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/config.py +138 -0
  142. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/errors.py +67 -0
  143. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +65 -0
  144. package/tools/vds-scripts/jira_orchestrator/tests/__init__.py +1 -0
  145. package/tools/vds-scripts/jira_orchestrator/tests/conftest.py +86 -0
  146. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  147. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_bulk_operations.py +69 -0
  148. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_components.py +57 -0
  149. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
  150. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_dashboard.py +117 -0
  151. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_issue_properties.py +54 -0
  152. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
  153. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
  154. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_remote_links.py +76 -0
  155. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_transitions.py +91 -0
  156. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_user_management.py +110 -0
  157. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_version_management.py +133 -0
  158. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_watchers.py +41 -0
  159. package/tools/vds-scripts/jira_orchestrator/tests/test_advanced_search.py +164 -0
  160. package/tools/vds-scripts/jira_orchestrator/tests/test_agile.py +256 -0
  161. package/tools/vds-scripts/jira_orchestrator/tests/test_application_properties.py +193 -0
  162. package/tools/vds-scripts/jira_orchestrator/tests/test_backlog.py +91 -0
  163. package/tools/vds-scripts/jira_orchestrator/tests/test_bulk_operations.py +277 -0
  164. package/tools/vds-scripts/jira_orchestrator/tests/test_cli.py +106 -0
  165. package/tools/vds-scripts/jira_orchestrator/tests/test_components.py +106 -0
  166. package/tools/vds-scripts/jira_orchestrator/tests/test_config.py +164 -0
  167. package/tools/vds-scripts/jira_orchestrator/tests/test_dashboard.py +122 -0
  168. package/tools/vds-scripts/jira_orchestrator/tests/test_discover_fields.py +207 -0
  169. package/tools/vds-scripts/jira_orchestrator/tests/test_filter_management.py +333 -0
  170. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_archiving.py +164 -0
  171. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_links.py +257 -0
  172. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_properties.py +171 -0
  173. package/tools/vds-scripts/jira_orchestrator/tests/test_link_types.py +314 -0
  174. package/tools/vds-scripts/jira_orchestrator/tests/test_parse_set.py +37 -0
  175. package/tools/vds-scripts/jira_orchestrator/tests/test_permissions.py +273 -0
  176. package/tools/vds-scripts/jira_orchestrator/tests/test_reindex.py +81 -0
  177. package/tools/vds-scripts/jira_orchestrator/tests/test_remote_links.py +254 -0
  178. package/tools/vds-scripts/jira_orchestrator/tests/test_security_schemes.py +170 -0
  179. package/tools/vds-scripts/jira_orchestrator/tests/test_transitions_changelog.py +114 -0
  180. package/tools/vds-scripts/jira_orchestrator/tests/test_user_management.py +226 -0
  181. package/tools/vds-scripts/jira_orchestrator/tests/test_version_management.py +339 -0
  182. package/tools/vds-scripts/jira_orchestrator/tests/test_watchers.py +101 -0
  183. package/tools/vds-scripts/jira_orchestrator/tests/test_worklog.py +223 -0
  184. package/tools/vds-scripts/jira_orchestrator/uv.lock +738 -0
  185. package/tools/vds-scripts/mcp_server/Dockerfile +34 -0
  186. package/tools/vds-scripts/mcp_server/README.md +140 -0
  187. package/tools/vds-scripts/mcp_server/pyproject.toml +42 -0
  188. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/__init__.py +4 -0
  189. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/config.py +36 -0
  190. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/server.py +66 -0
  191. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/__init__.py +14 -0
  192. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
  193. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +59 -0
  194. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
  195. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
  196. package/tools/vds-scripts/mcp_server/tests/__init__.py +2 -0
  197. package/tools/vds-scripts/mcp_server/tests/conftest.py +29 -0
  198. package/tools/vds-scripts/mcp_server/tests/unit/__init__.py +2 -0
  199. package/tools/vds-scripts/mcp_server/tests/unit/test_bitbucket_tools.py +25 -0
  200. package/tools/vds-scripts/mcp_server/tests/unit/test_confluence_tools.py +25 -0
  201. package/tools/vds-scripts/mcp_server/tests/unit/test_git_tools.py +32 -0
  202. package/tools/vds-scripts/mcp_server/tests/unit/test_jira_tools.py +32 -0
  203. package/tools/vds-scripts/mcp_server/tests/verification/__init__.py +2 -0
  204. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
  205. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_jira_tools.py +37 -0
  206. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_tool_registration.py +47 -0
  207. package/tools/vds-scripts/mcp_server/uv.lock +1032 -0
  208. package/tools/vds-scripts/mypy.ini +5 -0
  209. package/tools/vds-scripts/pyproject.toml +29 -0
  210. package/tools/vds-scripts/repo-manifest.yaml +273 -0
  211. package/tools/vds-scripts/repo-manifest.yaml.example +25 -0
  212. package/tools/vds-scripts/scripts/BRD-Validation-API.postman_collection.json +706 -0
  213. package/tools/vds-scripts/scripts/BRD-Validation-README.md +308 -0
  214. package/tools/vds-scripts/scripts/README.md +162 -0
  215. package/tools/vds-scripts/scripts/bootstrap_uv.sh +30 -0
  216. package/tools/vds-scripts/scripts/brd-validation-environment.json +51 -0
  217. package/tools/vds-scripts/scripts/brd-validation-test-results.json +13023 -0
  218. package/tools/vds-scripts/scripts/brd_coverage_report.json +276 -0
  219. package/tools/vds-scripts/scripts/create_memory_session.py +35 -0
  220. package/tools/vds-scripts/scripts/deployment/load_docker_images_offline.sh +90 -0
  221. package/tools/vds-scripts/scripts/final_completion_report.md +139 -0
  222. package/tools/vds-scripts/scripts/folder_structure_report.json +321 -0
  223. package/tools/vds-scripts/scripts/generate_completion_report.py +125 -0
  224. package/tools/vds-scripts/scripts/generate_intellij_modules.py +150 -0
  225. package/tools/vds-scripts/scripts/link_integrity_report.json +807 -0
  226. package/tools/vds-scripts/scripts/move_audit_artifact_pages.py +255 -0
  227. package/tools/vds-scripts/scripts/move_audit_artifact_pages_rest.py +165 -0
  228. package/tools/vds-scripts/scripts/move_wrong_dept_pages.py +216 -0
  229. package/tools/vds-scripts/scripts/save_intellij_memories.py +120 -0
  230. package/tools/vds-scripts/scripts/save_memories_to_vds_ai.py +83 -0
  231. package/tools/vds-scripts/scripts/save_memories_vds_style.py +129 -0
  232. package/tools/vds-scripts/scripts/search_intellij_memories.py +50 -0
  233. package/tools/vds-scripts/scripts/setup_intellij_workspace.py +65 -0
  234. package/tools/vds-scripts/scripts/target-state-automation/README.md +89 -0
  235. package/tools/vds-scripts/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
  236. package/tools/vds-scripts/scripts/target-state-automation/coordination.sh +114 -0
  237. package/tools/vds-scripts/scripts/target-state-automation/diagram_coordinator.sh +25 -0
  238. package/tools/vds-scripts/scripts/target-state-automation/docs_root.sh +22 -0
  239. package/tools/vds-scripts/scripts/target-state-automation/generate_diagrams.sh +22 -0
  240. package/tools/vds-scripts/scripts/target-state-automation/markdown_coordinator.sh +25 -0
  241. package/tools/vds-scripts/scripts/target-state-automation/progress_dashboard.sh +17 -0
  242. package/tools/vds-scripts/scripts/target-state-automation/schema_coordinator.sh +25 -0
  243. package/tools/vds-scripts/scripts/target-state-automation/sync_confluence.sh +30 -0
  244. package/tools/vds-scripts/scripts/target-state-automation/update_dependencies.sh +19 -0
  245. package/tools/vds-scripts/scripts/target-state-automation/validate_links.sh +86 -0
  246. package/tools/vds-scripts/scripts/target-state-automation/validate_markdown.sh +52 -0
  247. package/tools/vds-scripts/scripts/target-state-automation/validate_schemas.sh +26 -0
  248. package/tools/vds-scripts/scripts/target-state-automation/validate_structure.sh +98 -0
  249. package/tools/vds-scripts/scripts/update_modules_xml.py +190 -0
  250. package/tools/vds-scripts/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
  251. package/tools/vds-scripts/scripts/validate_brd_coverage.py +179 -0
  252. package/tools/vds-scripts/scripts/validate_folder_structure.py +240 -0
  253. package/tools/vds-scripts/scripts/validate_link_integrity.py +272 -0
  254. package/tools/vds-scripts/scripts/vds_sh_helpers.sh +180 -0
  255. package/tools/vds-scripts/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
  256. package/tools/vds-scripts/scripts/worktree_uv.sh +48 -0
  257. package/tools/vds-scripts/uv.lock +8 -0
  258. package/tools/vds-scripts/vds_cli/README.md +126 -0
  259. package/tools/vds-scripts/vds_cli/VERIFICATION_REPORT.md +41 -0
  260. package/tools/vds-scripts/vds_cli/pyproject.toml +38 -0
  261. package/tools/vds-scripts/vds_cli/src/vds_cli/__init__.py +3 -0
  262. package/tools/vds-scripts/vds_cli/src/vds_cli/cli.py +173 -0
  263. package/tools/vds-scripts/vds_cli/src/vds_cli/docs_sync.py +1203 -0
  264. package/tools/vds-scripts/vds_cli/src/vds_cli/env.py +41 -0
  265. package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
  266. package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +198 -0
  267. package/tools/vds-scripts/vds_cli/src/vds_cli/router.py +93 -0
  268. package/tools/vds-scripts/vds_cli/src/vds_cli/sync_api.py +647 -0
  269. package/tools/vds-scripts/vds_cli/src/vds_cli/sync_service.py +266 -0
  270. package/tools/vds-scripts/vds_cli/tests/__init__.py +2 -0
  271. package/tools/vds-scripts/vds_cli/tests/conftest.py +49 -0
  272. package/tools/vds-scripts/vds_cli/tests/unit/__init__.py +2 -0
  273. package/tools/vds-scripts/vds_cli/tests/unit/test_cli.py +143 -0
  274. package/tools/vds-scripts/vds_cli/tests/unit/test_docs_sync.py +422 -0
  275. package/tools/vds-scripts/vds_cli/tests/unit/test_env.py +51 -0
  276. package/tools/vds-scripts/vds_cli/tests/unit/test_router.py +72 -0
  277. package/tools/vds-scripts/vds_cli/tests/unit/test_sync_api.py +357 -0
  278. package/tools/vds-scripts/vds_cli/tests/unit/test_sync_service.py +160 -0
  279. package/tools/vds-scripts/vds_cli/tests/verification/__init__.py +2 -0
  280. package/tools/vds-scripts/vds_cli/tests/verification/test_bitbucket_real.py +33 -0
  281. package/tools/vds-scripts/vds_cli/tests/verification/test_confluence_real.py +35 -0
  282. package/tools/vds-scripts/vds_cli/tests/verification/test_jira_real.py +41 -0
  283. package/tools/vds-scripts/vds_cli/uv.lock +524 -0
  284. package/tools/vds-scripts/vds_cli_common/README.md +190 -0
  285. package/tools/vds-scripts/vds_cli_common/pyproject.toml +92 -0
  286. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/__init__.py +34 -0
  287. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/completers.py +139 -0
  288. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/context.py +201 -0
  289. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/env.py +119 -0
  290. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/errors.py +318 -0
  291. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/output.py +284 -0
  292. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/paths.py +78 -0
  293. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/testing.py +213 -0
  294. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/version.py +85 -0
  295. package/tools/vds-scripts/vds_cli_common/tests/__init__.py +1 -0
  296. package/tools/vds-scripts/vds_cli_common/tests/test_completers.py +148 -0
  297. package/tools/vds-scripts/vds_cli_common/tests/test_context.py +192 -0
  298. package/tools/vds-scripts/vds_cli_common/tests/test_env.py +102 -0
  299. package/tools/vds-scripts/vds_cli_common/tests/test_errors.py +186 -0
  300. package/tools/vds-scripts/vds_cli_common/tests/test_output.py +229 -0
  301. package/tools/vds-scripts/vds_cli_common/tests/test_paths.py +61 -0
  302. package/tools/vds-scripts/vds_cli_common/tests/test_testing.py +138 -0
  303. 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
+