@ngocsangairvds/vsaf 3.1.27 → 3.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/package.json +2 -2
  2. package/src/global.js +65 -39
  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,223 @@
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.errors import ConfluenceClientError
10
+ from confluence_orchestrator.http import ConfluenceClient
11
+ from typer.testing import CliRunner
12
+
13
+ runner = CliRunner()
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def mock_settings(monkeypatch: pytest.MonkeyPatch) -> None:
18
+ def fake_load_settings(strict: bool = False) -> ConfluenceSettings:
19
+ return ConfluenceSettings.model_validate(
20
+ {
21
+ "VDS_USERNAME": "user",
22
+ "VDS_PASSWORD": "pass",
23
+ "INTERNAL_CONFLUENCE_TOKEN": "token-int",
24
+ "EXTERNAL_CONFLUENCE_TOKEN": "token-ext",
25
+ }
26
+ )
27
+
28
+ monkeypatch.setattr("confluence_orchestrator.cli.load_settings", fake_load_settings)
29
+
30
+
31
+ def _build_fake_client(cloud: bool = False) -> tuple[ConfluenceClient, Mock]:
32
+ inner = Mock()
33
+ client = ConfluenceClient.__new__(ConfluenceClient) # type: ignore[call-arg]
34
+ client._client = inner # type: ignore[attr-defined]
35
+ client._log = Mock()
36
+ client._is_cloud = cloud # type: ignore[attr-defined]
37
+ client._with_retry = lambda operation, _: operation() # type: ignore[assignment]
38
+ return client, inner
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Client method coverage
43
+ # ---------------------------------------------------------------------------
44
+
45
+
46
+ def test_client_get_draft_page_by_id_server_mode_invokes_sdk() -> None:
47
+ client, inner = _build_fake_client(cloud=False)
48
+ inner.get_draft_page_by_id.return_value = {"id": "123", "title": "Draft Page"}
49
+
50
+ result = client.get_draft_page_by_id("123")
51
+
52
+ assert result["id"] == "123"
53
+ inner.get_draft_page_by_id.assert_called_once_with("123")
54
+
55
+
56
+ def test_client_get_draft_page_by_id_cloud_mode_raises_error() -> None:
57
+ client, inner = _build_fake_client(cloud=True)
58
+
59
+ with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
60
+ client.get_draft_page_by_id("123")
61
+ inner.get_draft_page_by_id.assert_not_called()
62
+
63
+
64
+ def test_client_get_all_draft_pages_from_space_server_mode_invokes_sdk() -> None:
65
+ client, inner = _build_fake_client(cloud=False)
66
+ inner.get_all_draft_pages_from_space.return_value = [{"id": "1", "title": "Draft 1"}, {"id": "2", "title": "Draft 2"}]
67
+
68
+ result = client.get_all_draft_pages_from_space("ABC", limit=50)
69
+
70
+ assert len(result) == 2
71
+ inner.get_all_draft_pages_from_space.assert_called_once_with("ABC", limit=50)
72
+
73
+
74
+ def test_client_get_all_draft_pages_from_space_cloud_mode_raises_error() -> None:
75
+ client, inner = _build_fake_client(cloud=True)
76
+
77
+ with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
78
+ client.get_all_draft_pages_from_space("ABC")
79
+ inner.get_all_draft_pages_from_space.assert_not_called()
80
+
81
+
82
+ def test_client_remove_page_as_draft_server_mode_invokes_sdk() -> None:
83
+ client, inner = _build_fake_client(cloud=False)
84
+ inner.remove_page_as_draft.return_value = None
85
+
86
+ result = client.remove_page_as_draft("123")
87
+
88
+ assert result is None
89
+ inner.remove_page_as_draft.assert_called_once_with("123")
90
+
91
+
92
+ def test_client_remove_page_as_draft_cloud_mode_raises_error() -> None:
93
+ client, inner = _build_fake_client(cloud=True)
94
+
95
+ with pytest.raises(ConfluenceClientError, match="only available in Confluence Server"):
96
+ client.remove_page_as_draft("123")
97
+ inner.remove_page_as_draft.assert_not_called()
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # CLI command coverage
102
+ # ---------------------------------------------------------------------------
103
+
104
+
105
+ class DummyDraftClient:
106
+ def __init__(self, cloud: bool = False) -> None:
107
+ self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
108
+ self._is_cloud = cloud # type: ignore[attr-defined]
109
+
110
+ def get_draft_page_by_id(self, page_id: str) -> dict[str, Any]:
111
+ if self._is_cloud:
112
+ raise ConfluenceClientError("Server-only feature")
113
+ self.calls.append(("get_draft_page_by_id", (page_id,), {}))
114
+ return {"id": page_id, "title": "Draft Page"}
115
+
116
+ def get_all_draft_pages_from_space(self, space_key: str, limit: int = 25) -> list[dict[str, Any]]:
117
+ if self._is_cloud:
118
+ raise ConfluenceClientError("Server-only feature")
119
+ self.calls.append(("get_all_draft_pages_from_space", (space_key,), {"limit": limit}))
120
+ return [{"id": "1", "title": "Draft 1"}]
121
+
122
+ def remove_page_as_draft(self, page_id: str) -> None:
123
+ if self._is_cloud:
124
+ raise ConfluenceClientError("Server-only feature")
125
+ self.calls.append(("remove_page_as_draft", (page_id,), {}))
126
+
127
+
128
+ def test_cli_draft_get_success(monkeypatch: pytest.MonkeyPatch) -> None:
129
+ client = DummyDraftClient(cloud=False)
130
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
131
+
132
+ result = runner.invoke(app, ["draft", "get", "--page-id", "123"])
133
+
134
+ assert result.exit_code == 0
135
+ assert "123" in result.stdout
136
+ assert len(client.calls) == 1
137
+ assert client.calls[0][0] == "get_draft_page_by_id"
138
+
139
+
140
+ def test_cli_draft_get_requires_page_id(monkeypatch: pytest.MonkeyPatch) -> None:
141
+ client = DummyDraftClient(cloud=False)
142
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
143
+
144
+ result = runner.invoke(app, ["draft", "get"])
145
+
146
+ assert result.exit_code != 0
147
+ assert "--page-id required" in result.stdout or "--page-id required" in result.stderr
148
+
149
+
150
+ def test_cli_draft_list_success(monkeypatch: pytest.MonkeyPatch) -> None:
151
+ client = DummyDraftClient(cloud=False)
152
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
153
+
154
+ result = runner.invoke(app, ["draft", "list", "--space-key", "ABC", "--limit", "50"])
155
+
156
+ assert result.exit_code == 0
157
+ assert "ABC" in result.stdout
158
+ assert len(client.calls) == 1
159
+ assert client.calls[0][0] == "get_all_draft_pages_from_space"
160
+ assert client.calls[0][2]["limit"] == 50
161
+
162
+
163
+ def test_cli_draft_list_requires_space_key(monkeypatch: pytest.MonkeyPatch) -> None:
164
+ client = DummyDraftClient(cloud=False)
165
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
166
+
167
+ result = runner.invoke(app, ["draft", "list"])
168
+
169
+ assert result.exit_code != 0
170
+ assert "--space-key required" in result.stdout or "--space-key required" in result.stderr
171
+
172
+
173
+ def test_cli_draft_remove_success(monkeypatch: pytest.MonkeyPatch) -> None:
174
+ client = DummyDraftClient(cloud=False)
175
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
176
+
177
+ result = runner.invoke(app, ["draft", "remove", "--page-id", "123", "--yes"])
178
+
179
+ assert result.exit_code == 0
180
+ assert "removed" in result.stdout
181
+ assert len(client.calls) == 1
182
+ assert client.calls[0][0] == "remove_page_as_draft"
183
+
184
+
185
+ def test_cli_draft_remove_requires_yes(monkeypatch: pytest.MonkeyPatch) -> None:
186
+ client = DummyDraftClient(cloud=False)
187
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
188
+
189
+ result = runner.invoke(app, ["draft", "remove", "--page-id", "123"])
190
+
191
+ assert result.exit_code != 0
192
+ assert "--yes required" in result.stdout or "--yes required" in result.stderr
193
+
194
+
195
+ def test_cli_draft_remove_requires_page_id(monkeypatch: pytest.MonkeyPatch) -> None:
196
+ client = DummyDraftClient(cloud=False)
197
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
198
+
199
+ result = runner.invoke(app, ["draft", "remove", "--yes"])
200
+
201
+ assert result.exit_code != 0
202
+ assert "--page-id required" in result.stdout or "--page-id required" in result.stderr
203
+
204
+
205
+ def test_cli_draft_cloud_mode_error(monkeypatch: pytest.MonkeyPatch) -> None:
206
+ client = DummyDraftClient(cloud=True)
207
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
208
+
209
+ result = runner.invoke(app, ["draft", "get", "--page-id", "123"])
210
+
211
+ assert result.exit_code != 0
212
+ assert "Server-only" in result.stderr or "Server-only" in result.stdout
213
+
214
+
215
+ def test_cli_draft_invalid_action(monkeypatch: pytest.MonkeyPatch) -> None:
216
+ client = DummyDraftClient(cloud=False)
217
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
218
+
219
+ result = runner.invoke(app, ["draft", "invalid"])
220
+
221
+ assert result.exit_code != 0
222
+ assert "Unknown action" in result.stdout or "Unknown action" in result.stderr
223
+
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from confluence_orchestrator.eventing import Poller
7
+
8
+
9
+ class FakeClient:
10
+ def __init__(self, responses: list[dict[str, Any]]) -> None:
11
+ self._responses = responses
12
+ self.calls = 0
13
+
14
+ def search_cql(self, *_, **kwargs) -> dict[str, Any]: # noqa: ANN401
15
+ limit = kwargs.get("limit")
16
+ if self.calls < len(self._responses):
17
+ resp = self._responses[self.calls]
18
+ payload = dict(resp)
19
+ if limit is not None and "results" in payload:
20
+ payload["results"] = payload["results"][:limit]
21
+ else:
22
+ payload = {"results": [], "start": 0, "limit": 0}
23
+ self.calls += 1
24
+ return payload
25
+
26
+
27
+ def test_poller_updates_cursor(tmp_path: Path) -> None:
28
+ client = FakeClient(
29
+ [
30
+ {
31
+ "results": [
32
+ {"id": "10", "version": {"when": "2024-01-02T00:00:00Z"}},
33
+ {"id": "11", "version": {"when": "2024-01-03T00:00:00Z"}},
34
+ ],
35
+ "start": 0,
36
+ "limit": 25,
37
+ }
38
+ ]
39
+ )
40
+
41
+ poller = Poller(client) # type: ignore[arg-type]
42
+ state_file = tmp_path / "cursor.json"
43
+ count = poller.poll_once(base_cql="type=page", state_file=state_file, expand=["version"], out_jsonl=tmp_path / "out.jsonl")
44
+
45
+ assert count == 2
46
+ data = state_file.read_text()
47
+ assert "2024-01-03T00:00:00Z" in data
48
+ jsonl_contents = (tmp_path / "out.jsonl").read_text().strip().splitlines()
49
+ assert len(jsonl_contents) == 2
50
+
51
+
52
+ def test_poller_limits_results(tmp_path: Path) -> None:
53
+ client = FakeClient(
54
+ [
55
+ {
56
+ "results": [
57
+ {"id": "10", "version": {"when": "2024-01-02T00:00:00Z"}},
58
+ {"id": "11", "version": {"when": "2024-01-03T00:00:00Z"}},
59
+ ],
60
+ "start": 0,
61
+ "limit": 25,
62
+ }
63
+ ]
64
+ )
65
+
66
+ poller = Poller(client) # type: ignore[arg-type]
67
+ state_file = tmp_path / "cursor.json"
68
+ count = poller.poll_once(base_cql="type=page", state_file=state_file, max_results=1)
69
+
70
+ assert count == 1
71
+ assert "2024-01-02T00:00:00Z" in state_file.read_text()
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from confluence_orchestrator.eventing import Poller
7
+
8
+
9
+ class SingleRunClient:
10
+ def __init__(self, results: list[dict[str, Any]]) -> None:
11
+ self._results = results
12
+ self.calls = 0
13
+
14
+ def search_cql(self, *_, **kwargs) -> dict[str, Any]: # noqa: ANN401
15
+ limit = kwargs.get("limit")
16
+ if self.calls == 0:
17
+ payload = {"results": self._results, "start": 0, "limit": len(self._results)}
18
+ if limit is not None:
19
+ payload["results"] = self._results[:limit]
20
+ else:
21
+ payload = {"results": [], "start": 0, "limit": 0}
22
+ self.calls += 1
23
+ return payload
24
+
25
+
26
+ def test_poller_persists_state_across_runs(tmp_path: Path) -> None:
27
+ state_file = tmp_path / "cursor.json"
28
+
29
+ poller = Poller(SingleRunClient([{ "id": "10", "version": {"when": "2024-01-02T00:00:00Z"}}])) # type: ignore[arg-type]
30
+ poller.poll_once(base_cql="type=page", state_file=state_file)
31
+ assert "2024-01-02T00:00:00Z" in state_file.read_text()
32
+
33
+ poller = Poller(SingleRunClient([{ "id": "11", "version": {"when": "2024-01-04T00:00:00Z"}}])) # type: ignore[arg-type]
34
+ poller.poll_once(base_cql="type=page", state_file=state_file)
35
+ contents = state_file.read_text()
36
+ assert "2024-01-04T00:00:00Z" in contents
37
+ assert "\"10\"" in contents and "\"11\"" in contents
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from confluence_orchestrator.eventing import Poller
7
+
8
+
9
+ class DuplicateClient:
10
+ def __init__(self) -> None:
11
+ self.responses: list[dict[str, Any]] = [
12
+ {
13
+ "results": [
14
+ {"id": "10", "version": {"when": "2024-01-02T00:00:00Z"}},
15
+ {"id": "10", "version": {"when": "2024-01-02T00:00:00Z"}},
16
+ {"id": "11", "version": {"when": "2024-01-03T00:00:00Z"}},
17
+ ],
18
+ "start": 0,
19
+ "limit": 25,
20
+ }
21
+ ]
22
+ self.calls = 0
23
+
24
+ def search_cql(self, *_, **kwargs) -> dict[str, Any]: # noqa: ANN401
25
+ limit = kwargs.get("limit")
26
+ if self.calls == 0:
27
+ resp = dict(self.responses[0])
28
+ if limit is not None:
29
+ resp["results"] = resp["results"][:limit]
30
+ else:
31
+ resp = {"results": [], "start": 0, "limit": 0}
32
+ self.calls += 1
33
+ return resp
34
+
35
+
36
+ def test_poller_skips_duplicate_ids(tmp_path: Path) -> None:
37
+ client = DuplicateClient()
38
+ poller = Poller(client) # type: ignore[arg-type]
39
+
40
+ state_file = tmp_path / "cursor.json"
41
+ count = poller.poll_once(base_cql="type=page", state_file=state_file, limit=10)
42
+
43
+ assert count == 2 # one duplicate skipped
44
+ assert client.calls == 2 # second call returns empty page
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from confluence_orchestrator.eventing import Poller
7
+
8
+
9
+ class PagedClient:
10
+ def __init__(self) -> None:
11
+ self.responses: list[dict[str, Any]] = [
12
+ {
13
+ "results": [
14
+ {"id": "10", "version": {"when": "2024-01-02T00:00:00Z"}},
15
+ ],
16
+ "start": 0,
17
+ "limit": 1,
18
+ },
19
+ {
20
+ "results": [
21
+ {"id": "11", "version": {"when": "2024-01-03T00:00:00Z"}},
22
+ ],
23
+ "start": 1,
24
+ "limit": 1,
25
+ },
26
+ ]
27
+ self.calls = 0
28
+
29
+ def search_cql(self, *_, **kwargs) -> dict[str, Any]: # noqa: ANN401
30
+ limit = kwargs.get("limit")
31
+ if self.calls < len(self.responses):
32
+ resp = dict(self.responses[self.calls])
33
+ if limit is not None:
34
+ resp["results"] = resp["results"][:limit]
35
+ else:
36
+ resp = {"results": [], "start": 0, "limit": 0}
37
+ self.calls += 1
38
+ return resp
39
+
40
+
41
+ def test_poller_respects_max_results(tmp_path: Path) -> None:
42
+ client = PagedClient()
43
+ poller = Poller(client) # type: ignore[arg-type]
44
+ state_file = tmp_path / "cursor.json"
45
+
46
+ count = poller.poll_once(base_cql="type=page", state_file=state_file, limit=1, max_results=1)
47
+
48
+ assert count == 1
49
+ assert client.calls == 1
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+ from unittest.mock import Mock
6
+
7
+ import pytest
8
+ from confluence_orchestrator.cli import app
9
+ from confluence_orchestrator.config import ConfluenceSettings
10
+ from confluence_orchestrator.http import ConfluenceClient
11
+ from typer.testing import CliRunner
12
+
13
+ runner = CliRunner()
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def mock_settings(monkeypatch: pytest.MonkeyPatch) -> None:
18
+ def fake_load_settings(strict: bool = False) -> ConfluenceSettings:
19
+ return ConfluenceSettings.model_validate(
20
+ {
21
+ "VDS_USERNAME": "user",
22
+ "VDS_PASSWORD": "pass",
23
+ "INTERNAL_CONFLUENCE_TOKEN": "token-int",
24
+ "EXTERNAL_CONFLUENCE_TOKEN": "token-ext",
25
+ }
26
+ )
27
+
28
+ monkeypatch.setattr("confluence_orchestrator.cli.load_settings", fake_load_settings)
29
+
30
+
31
+ def _build_fake_client() -> tuple[ConfluenceClient, Mock]:
32
+ inner = Mock()
33
+ client = ConfluenceClient.__new__(ConfluenceClient) # type: ignore[call-arg]
34
+ client._client = inner # type: ignore[attr-defined]
35
+ client._log = Mock()
36
+ client._is_cloud = True # type: ignore[attr-defined]
37
+ client._with_retry = lambda operation, _: operation() # type: ignore[assignment]
38
+ return client, inner
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Client method coverage
43
+ # ---------------------------------------------------------------------------
44
+
45
+
46
+ def test_client_export_page_cloud_mode_invokes_sdk() -> None:
47
+ client, inner = _build_fake_client()
48
+ inner.export_page.return_value = b"%PDF-1.4 fake pdf content"
49
+
50
+ result = client.export_page("12345")
51
+
52
+ assert result == b"%PDF-1.4 fake pdf content"
53
+ # SDK auto-detects Cloud/Server mode, so api_version is not passed
54
+ inner.export_page.assert_called_once_with("12345")
55
+
56
+
57
+ def test_client_export_page_server_mode_invokes_sdk() -> None:
58
+ client, inner = _build_fake_client()
59
+ client._is_cloud = False # type: ignore[attr-defined]
60
+ inner.export_page.return_value = b"%PDF-1.4 fake pdf content"
61
+
62
+ result = client.export_page("12345")
63
+
64
+ assert result == b"%PDF-1.4 fake pdf content"
65
+ # SDK auto-detects Cloud/Server mode, so api_version is not passed
66
+ inner.export_page.assert_called_once_with("12345")
67
+
68
+
69
+ def test_client_export_page_with_explicit_api_version() -> None:
70
+ client, inner = _build_fake_client()
71
+ inner.export_page.return_value = b"%PDF-1.4 fake pdf content"
72
+
73
+ result = client.export_page("12345", api_version="server")
74
+
75
+ assert result == b"%PDF-1.4 fake pdf content"
76
+ # SDK auto-detects Cloud/Server mode, so api_version parameter is accepted but not passed to SDK
77
+ inner.export_page.assert_called_once_with("12345")
78
+
79
+
80
+ def test_client_export_page_handles_file_path() -> None:
81
+ client, inner = _build_fake_client()
82
+ # Mock file path return
83
+ import tempfile
84
+
85
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
86
+ tmp.write(b"%PDF-1.4 fake pdf content")
87
+ tmp_path = tmp.name
88
+
89
+ inner.export_page.return_value = tmp_path
90
+
91
+ result = client.export_page("12345")
92
+
93
+ assert result == b"%PDF-1.4 fake pdf content"
94
+ # Cleanup
95
+ Path(tmp_path).unlink()
96
+
97
+
98
+ def test_client_get_space_export_invokes_sdk() -> None:
99
+ client, inner = _build_fake_client()
100
+ inner.get_space_export.return_value = "https://example.com/export/space.pdf"
101
+
102
+ result = client.get_space_export("ABC", "pdf")
103
+
104
+ assert result == "https://example.com/export/space.pdf"
105
+ inner.get_space_export.assert_called_once_with("ABC", "pdf")
106
+
107
+
108
+ def test_client_get_space_export_with_different_types() -> None:
109
+ client, inner = _build_fake_client()
110
+ inner.get_space_export.return_value = "https://example.com/export/space.html"
111
+
112
+ result = client.get_space_export("ABC", "html")
113
+
114
+ assert result == "https://example.com/export/space.html"
115
+ inner.get_space_export.assert_called_once_with("ABC", "html")
116
+
117
+
118
+ # ---------------------------------------------------------------------------
119
+ # CLI command coverage
120
+ # ---------------------------------------------------------------------------
121
+
122
+
123
+ class DummyExportClient:
124
+ def __init__(self) -> None:
125
+ self.calls: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
126
+
127
+ def export_page(self, page_id: str, api_version: str | None = None) -> bytes:
128
+ self.calls.append(("export_page", (page_id,), {"api_version": api_version}))
129
+ return b"%PDF-1.4 fake pdf content"
130
+
131
+ def get_space_export(self, space_key: str, export_type: str) -> str:
132
+ self.calls.append(("get_space_export", (space_key, export_type), {}))
133
+ return f"https://example.com/export/{space_key}.{export_type}"
134
+
135
+
136
+ def test_cli_export_page_success(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
137
+ client = DummyExportClient()
138
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
139
+
140
+ output_file = tmp_path / "test.pdf"
141
+ result = runner.invoke(app, ["export", "page", "--page-id", "12345", "--output", str(output_file)])
142
+
143
+ assert result.exit_code == 0
144
+ assert output_file.exists()
145
+ assert output_file.read_bytes() == b"%PDF-1.4 fake pdf content"
146
+ assert len(client.calls) == 1
147
+ assert client.calls[0][0] == "export_page"
148
+
149
+
150
+ def test_cli_export_page_requires_page_id(monkeypatch: pytest.MonkeyPatch) -> None:
151
+ client = DummyExportClient()
152
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
153
+
154
+ result = runner.invoke(app, ["export", "page"])
155
+
156
+ assert result.exit_code != 0
157
+ assert "--page-id required" in result.stdout or "--page-id required" in result.stderr
158
+
159
+
160
+ def test_cli_export_page_with_api_version(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
161
+ client = DummyExportClient()
162
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
163
+
164
+ output_file = tmp_path / "test.pdf"
165
+ result = runner.invoke(app, ["export", "page", "--page-id", "12345", "--api-version", "server", "--output", str(output_file)])
166
+
167
+ assert result.exit_code == 0
168
+ assert client.calls[0][2]["api_version"] == "server"
169
+
170
+
171
+ def test_cli_export_space_success(monkeypatch: pytest.MonkeyPatch) -> None:
172
+ client = DummyExportClient()
173
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
174
+
175
+ result = runner.invoke(app, ["export", "space", "--space-key", "ABC", "--export-type", "pdf"])
176
+
177
+ assert result.exit_code == 0
178
+ assert "ABC" in result.stdout or "download_url" in result.stdout
179
+ assert len(client.calls) == 1
180
+ assert client.calls[0][0] == "get_space_export"
181
+
182
+
183
+ def test_cli_export_space_requires_space_key(monkeypatch: pytest.MonkeyPatch) -> None:
184
+ client = DummyExportClient()
185
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
186
+
187
+ result = runner.invoke(app, ["export", "space", "--export-type", "pdf"])
188
+
189
+ assert result.exit_code != 0
190
+ assert "--space-key required" in result.stdout or "--space-key required" in result.stderr
191
+
192
+
193
+ def test_cli_export_space_requires_export_type(monkeypatch: pytest.MonkeyPatch) -> None:
194
+ client = DummyExportClient()
195
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
196
+
197
+ result = runner.invoke(app, ["export", "space", "--space-key", "ABC"])
198
+
199
+ assert result.exit_code != 0
200
+ assert "--export-type required" in result.stdout or "--export-type required" in result.stderr
201
+
202
+
203
+ def test_cli_export_space_validates_export_type(monkeypatch: pytest.MonkeyPatch) -> None:
204
+ client = DummyExportClient()
205
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
206
+
207
+ result = runner.invoke(app, ["export", "space", "--space-key", "ABC", "--export-type", "invalid"])
208
+
209
+ assert result.exit_code != 0
210
+ assert "must be one of" in result.stdout or "must be one of" in result.stderr
211
+
212
+
213
+ def test_cli_export_space_with_different_types(monkeypatch: pytest.MonkeyPatch) -> None:
214
+ client = DummyExportClient()
215
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
216
+
217
+ for export_type in ("pdf", "html", "xml"):
218
+ result = runner.invoke(app, ["export", "space", "--space-key", "ABC", "--export-type", export_type])
219
+ assert result.exit_code == 0
220
+
221
+
222
+ def test_cli_export_invalid_action(monkeypatch: pytest.MonkeyPatch) -> None:
223
+ client = DummyExportClient()
224
+ monkeypatch.setattr("confluence_orchestrator.cli._build_http_client", lambda *args: client)
225
+
226
+ result = runner.invoke(app, ["export", "invalid"])
227
+
228
+ assert result.exit_code != 0
229
+ assert "Unknown action" in result.stdout or "Unknown action" in result.stderr
230
+