@datarecce/ui 0.1.30 → 0.1.31

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 (549) hide show
  1. package/dist/api.d.mts +1 -1
  2. package/dist/api.d.ts +1 -1
  3. package/dist/components.d.mts +1 -1
  4. package/dist/components.d.ts +1 -1
  5. package/dist/hooks.d.mts +1 -1
  6. package/dist/hooks.d.ts +1 -1
  7. package/dist/{index-BNUP2V_N.d.ts → index-B9lSPJTi.d.ts} +184 -2
  8. package/dist/index-B9lSPJTi.d.ts.map +1 -0
  9. package/dist/{index-DOPZuhD8.d.mts → index-IIXVIoOL.d.mts} +253 -71
  10. package/dist/index-IIXVIoOL.d.mts.map +1 -0
  11. package/dist/index.d.mts +2 -2
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +8 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +3 -2
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/styles.css +4 -0
  18. package/dist/theme.d.mts +2 -185
  19. package/dist/theme.d.ts +2 -185
  20. package/dist/types.d.mts +1 -1
  21. package/dist/types.d.ts +1 -1
  22. package/package.json +4 -2
  23. package/recce-source/.editorconfig +26 -0
  24. package/recce-source/.flake8 +37 -0
  25. package/recce-source/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
  26. package/recce-source/.github/ISSUE_TEMPLATE/custom.md +10 -0
  27. package/recce-source/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
  28. package/recce-source/.github/PULL_REQUEST_TEMPLATE.md +21 -0
  29. package/recce-source/.github/copilot-instructions.md +331 -0
  30. package/recce-source/.github/instructions/backend-instructions.md +541 -0
  31. package/recce-source/.github/instructions/frontend-instructions.md +317 -0
  32. package/recce-source/.github/workflows/build-statics.yaml +72 -0
  33. package/recce-source/.github/workflows/bump.yaml +48 -0
  34. package/recce-source/.github/workflows/integration-tests-cloud.yaml +92 -0
  35. package/recce-source/.github/workflows/integration-tests-sqlmesh.yaml +33 -0
  36. package/recce-source/.github/workflows/integration-tests.yaml +52 -0
  37. package/recce-source/.github/workflows/nightly.yaml +246 -0
  38. package/recce-source/.github/workflows/release.yaml +196 -0
  39. package/recce-source/.github/workflows/tests-js.yaml +58 -0
  40. package/recce-source/.github/workflows/tests-python.yaml +128 -0
  41. package/recce-source/.pre-commit-config.yaml +26 -0
  42. package/recce-source/CLAUDE.md +483 -0
  43. package/recce-source/CODE_OF_CONDUCT.md +128 -0
  44. package/recce-source/CONTRIBUTING.md +107 -0
  45. package/recce-source/LICENSE +201 -0
  46. package/recce-source/Makefile +126 -0
  47. package/recce-source/README.md +182 -0
  48. package/recce-source/RECCE_CLOUD.md +81 -0
  49. package/recce-source/SECURITY.md +25 -0
  50. package/recce-source/docs/PACKAGING.md +340 -0
  51. package/recce-source/docs/README.md +1 -0
  52. package/recce-source/integration_tests/dbt/dbt_project.yml +26 -0
  53. package/recce-source/integration_tests/dbt/models/customers.sql +69 -0
  54. package/recce-source/integration_tests/dbt/models/docs.md +14 -0
  55. package/recce-source/integration_tests/dbt/models/orders.sql +56 -0
  56. package/recce-source/integration_tests/dbt/models/schema.yml +82 -0
  57. package/recce-source/integration_tests/dbt/models/staging/schema.yml +31 -0
  58. package/recce-source/integration_tests/dbt/models/staging/stg_customers.sql +22 -0
  59. package/recce-source/integration_tests/dbt/models/staging/stg_orders.sql +23 -0
  60. package/recce-source/integration_tests/dbt/models/staging/stg_payments.sql +25 -0
  61. package/recce-source/integration_tests/dbt/packages.yml +7 -0
  62. package/recce-source/integration_tests/dbt/profiles.yml +8 -0
  63. package/recce-source/integration_tests/dbt/seeds/raw_customers.csv +101 -0
  64. package/recce-source/integration_tests/dbt/seeds/raw_orders.csv +100 -0
  65. package/recce-source/integration_tests/dbt/seeds/raw_payments.csv +114 -0
  66. package/recce-source/integration_tests/dbt/seeds/raw_statuses.csv +5 -0
  67. package/recce-source/integration_tests/dbt/smoke_test.sh +72 -0
  68. package/recce-source/integration_tests/dbt/smoke_test_cloud.sh +71 -0
  69. package/recce-source/integration_tests/sqlmesh/__init__.py +0 -0
  70. package/recce-source/integration_tests/sqlmesh/audits/assert_item_price_above_zero.sql +9 -0
  71. package/recce-source/integration_tests/sqlmesh/audits/items.sql +7 -0
  72. package/recce-source/integration_tests/sqlmesh/audits/order_items.sql +7 -0
  73. package/recce-source/integration_tests/sqlmesh/config.py +171 -0
  74. package/recce-source/integration_tests/sqlmesh/helper.py +20 -0
  75. package/recce-source/integration_tests/sqlmesh/hooks/__init__.py +0 -0
  76. package/recce-source/integration_tests/sqlmesh/macros/__init__.py +0 -0
  77. package/recce-source/integration_tests/sqlmesh/macros/macros.py +8 -0
  78. package/recce-source/integration_tests/sqlmesh/macros/macros.sql +8 -0
  79. package/recce-source/integration_tests/sqlmesh/macros/utils.py +11 -0
  80. package/recce-source/integration_tests/sqlmesh/metrics/metrics.sql +25 -0
  81. package/recce-source/integration_tests/sqlmesh/models/customer_revenue_by_day.sql +41 -0
  82. package/recce-source/integration_tests/sqlmesh/models/customer_revenue_lifetime.sql +60 -0
  83. package/recce-source/integration_tests/sqlmesh/models/customers.sql +32 -0
  84. package/recce-source/integration_tests/sqlmesh/models/items.py +95 -0
  85. package/recce-source/integration_tests/sqlmesh/models/marketing.sql +15 -0
  86. package/recce-source/integration_tests/sqlmesh/models/order_items.py +95 -0
  87. package/recce-source/integration_tests/sqlmesh/models/orders.py +70 -0
  88. package/recce-source/integration_tests/sqlmesh/models/raw_marketing.py +62 -0
  89. package/recce-source/integration_tests/sqlmesh/models/top_waiters.sql +23 -0
  90. package/recce-source/integration_tests/sqlmesh/models/waiter_as_customer_by_day.sql +29 -0
  91. package/recce-source/integration_tests/sqlmesh/models/waiter_names.sql +10 -0
  92. package/recce-source/integration_tests/sqlmesh/models/waiter_revenue_by_day.sql +29 -0
  93. package/recce-source/integration_tests/sqlmesh/models/waiters.py +62 -0
  94. package/recce-source/integration_tests/sqlmesh/prep_env.sh +16 -0
  95. package/recce-source/integration_tests/sqlmesh/schema.yaml +5 -0
  96. package/recce-source/integration_tests/sqlmesh/seeds/waiter_names.csv +11 -0
  97. package/recce-source/integration_tests/sqlmesh/test_server.sh +29 -0
  98. package/recce-source/integration_tests/sqlmesh/tests/test_customer_revenue_by_day.yaml +63 -0
  99. package/recce-source/integration_tests/sqlmesh/tests/test_order_items.yaml +72 -0
  100. package/recce-source/js/.editorconfig +27 -0
  101. package/recce-source/js/.env.development +5 -0
  102. package/recce-source/js/.husky/pre-commit +29 -0
  103. package/recce-source/js/.nvmrc +1 -0
  104. package/recce-source/js/README.md +39 -0
  105. package/recce-source/js/app/(mainComponents)/DisplayModeToggle.tsx +65 -0
  106. package/recce-source/js/app/(mainComponents)/NavBar.tsx +228 -0
  107. package/recce-source/js/app/(mainComponents)/RecceVersionBadge.tsx +107 -0
  108. package/recce-source/js/app/(mainComponents)/TopBar.tsx +252 -0
  109. package/recce-source/js/app/@lineage/default.tsx +20 -0
  110. package/recce-source/js/app/@lineage/page.tsx +14 -0
  111. package/recce-source/js/app/MainLayout.tsx +170 -0
  112. package/recce-source/js/app/Providers.tsx +49 -0
  113. package/recce-source/js/app/checks/page.tsx +296 -0
  114. package/recce-source/js/app/error.tsx +93 -0
  115. package/recce-source/js/app/favicon.ico +0 -0
  116. package/recce-source/js/app/global-error.tsx +115 -0
  117. package/recce-source/js/app/global.css +82 -0
  118. package/recce-source/js/app/layout.tsx +48 -0
  119. package/recce-source/js/app/lineage/page.tsx +15 -0
  120. package/recce-source/js/app/page.tsx +12 -0
  121. package/recce-source/js/app/query/page.tsx +8 -0
  122. package/recce-source/js/biome.json +313 -0
  123. package/recce-source/js/jest.config.js +34 -0
  124. package/recce-source/js/jest.globals.d.ts +32 -0
  125. package/recce-source/js/jest.setup.js +91 -0
  126. package/recce-source/js/next.config.js +16 -0
  127. package/recce-source/js/package-lock.json +13843 -0
  128. package/recce-source/js/package.json +123 -0
  129. package/recce-source/js/pnpm-lock.yaml +9235 -0
  130. package/recce-source/js/pnpm-workspace.yaml +6 -0
  131. package/recce-source/js/postcss.config.js +5 -0
  132. package/recce-source/js/public/auth_callback.html +68 -0
  133. package/recce-source/js/public/imgs/feedback/thumbs-down.png +0 -0
  134. package/recce-source/js/public/imgs/feedback/thumbs-up.png +0 -0
  135. package/recce-source/js/public/imgs/reload-image.svg +4 -0
  136. package/recce-source/js/public/logo/recce-logo-white.png +0 -0
  137. package/recce-source/js/src/components/AuthModal/AuthModal.tsx +202 -0
  138. package/recce-source/js/src/components/app/AvatarDropdown.tsx +159 -0
  139. package/recce-source/js/src/components/app/EnvInfo.tsx +357 -0
  140. package/recce-source/js/src/components/app/Filename.tsx +388 -0
  141. package/recce-source/js/src/components/app/SetupConnectionPopover.tsx +91 -0
  142. package/recce-source/js/src/components/app/StateExporter.tsx +57 -0
  143. package/recce-source/js/src/components/app/StateImporter.tsx +198 -0
  144. package/recce-source/js/src/components/app/StateSharing.tsx +145 -0
  145. package/recce-source/js/src/components/app/StateSynchronizer.tsx +205 -0
  146. package/recce-source/js/src/components/charts/HistogramChart.tsx +291 -0
  147. package/recce-source/js/src/components/charts/SquareIcon.tsx +51 -0
  148. package/recce-source/js/src/components/charts/TopKSummaryList.tsx +457 -0
  149. package/recce-source/js/src/components/charts/chartTheme.ts +74 -0
  150. package/recce-source/js/src/components/check/CheckBreadcrumb.tsx +97 -0
  151. package/recce-source/js/src/components/check/CheckDescription.tsx +134 -0
  152. package/recce-source/js/src/components/check/CheckDetail.tsx +797 -0
  153. package/recce-source/js/src/components/check/CheckEmptyState.tsx +84 -0
  154. package/recce-source/js/src/components/check/CheckList.tsx +320 -0
  155. package/recce-source/js/src/components/check/LineageDiffView.tsx +32 -0
  156. package/recce-source/js/src/components/check/PresetCheckTemplateView.tsx +48 -0
  157. package/recce-source/js/src/components/check/SchemaDiffView.tsx +290 -0
  158. package/recce-source/js/src/components/check/check.ts +25 -0
  159. package/recce-source/js/src/components/check/timeline/CheckTimeline.tsx +163 -0
  160. package/recce-source/js/src/components/check/timeline/CommentInput.tsx +84 -0
  161. package/recce-source/js/src/components/check/timeline/TimelineEvent.tsx +468 -0
  162. package/recce-source/js/src/components/check/timeline/index.ts +12 -0
  163. package/recce-source/js/src/components/check/utils.ts +12 -0
  164. package/recce-source/js/src/components/data-grid/ScreenshotDataGrid.tsx +333 -0
  165. package/recce-source/js/src/components/data-grid/agGridStyles.css +55 -0
  166. package/recce-source/js/src/components/data-grid/agGridTheme.ts +43 -0
  167. package/recce-source/js/src/components/editor/CodeEditor.tsx +107 -0
  168. package/recce-source/js/src/components/editor/DiffEditor.tsx +162 -0
  169. package/recce-source/js/src/components/editor/index.ts +12 -0
  170. package/recce-source/js/src/components/errorboundary/ErrorBoundary.tsx +87 -0
  171. package/recce-source/js/src/components/histogram/HistogramDiffForm.tsx +147 -0
  172. package/recce-source/js/src/components/histogram/HistogramDiffResultView.tsx +63 -0
  173. package/recce-source/js/src/components/icons/index.tsx +142 -0
  174. package/recce-source/js/src/components/lineage/ActionControl.tsx +63 -0
  175. package/recce-source/js/src/components/lineage/ActionTag.tsx +141 -0
  176. package/recce-source/js/src/components/lineage/ChangeStatusLegend.tsx +46 -0
  177. package/recce-source/js/src/components/lineage/ColumnLevelLineageControl.tsx +327 -0
  178. package/recce-source/js/src/components/lineage/ColumnLevelLineageLegend.tsx +57 -0
  179. package/recce-source/js/src/components/lineage/GraphColumnNode.tsx +199 -0
  180. package/recce-source/js/src/components/lineage/GraphEdge.tsx +59 -0
  181. package/recce-source/js/src/components/lineage/GraphNode.tsx +555 -0
  182. package/recce-source/js/src/components/lineage/LineagePage.tsx +10 -0
  183. package/recce-source/js/src/components/lineage/LineageView.tsx +1384 -0
  184. package/recce-source/js/src/components/lineage/LineageViewContext.tsx +86 -0
  185. package/recce-source/js/src/components/lineage/LineageViewContextMenu.tsx +637 -0
  186. package/recce-source/js/src/components/lineage/LineageViewNotification.tsx +64 -0
  187. package/recce-source/js/src/components/lineage/LineageViewTopBar.tsx +596 -0
  188. package/recce-source/js/src/components/lineage/NodeSqlView.tsx +136 -0
  189. package/recce-source/js/src/components/lineage/NodeTag.tsx +278 -0
  190. package/recce-source/js/src/components/lineage/NodeView.tsx +642 -0
  191. package/recce-source/js/src/components/lineage/SandboxView.tsx +436 -0
  192. package/recce-source/js/src/components/lineage/ServerDisconnectedModalContent.tsx +105 -0
  193. package/recce-source/js/src/components/lineage/SetupConnectionBanner.tsx +52 -0
  194. package/recce-source/js/src/components/lineage/SingleEnvironmentQueryView.tsx +152 -0
  195. package/recce-source/js/src/components/lineage/graph.test.ts +31 -0
  196. package/recce-source/js/src/components/lineage/graph.ts +58 -0
  197. package/recce-source/js/src/components/lineage/lineage.test.ts +169 -0
  198. package/recce-source/js/src/components/lineage/lineage.ts +521 -0
  199. package/recce-source/js/src/components/lineage/styles.css +42 -0
  200. package/recce-source/js/src/components/lineage/styles.tsx +165 -0
  201. package/recce-source/js/src/components/lineage/useMultiNodesAction.ts +352 -0
  202. package/recce-source/js/src/components/lineage/useValueDiffAlertDialog.tsx +108 -0
  203. package/recce-source/js/src/components/onboarding-guide/Notification.tsx +62 -0
  204. package/recce-source/js/src/components/profile/ProfileDiffForm.tsx +134 -0
  205. package/recce-source/js/src/components/profile/ProfileDiffResultView.tsx +245 -0
  206. package/recce-source/js/src/components/query/ChangedOnlyCheckbox.tsx +29 -0
  207. package/recce-source/js/src/components/query/DiffText.tsx +120 -0
  208. package/recce-source/js/src/components/query/QueryDiffResultView.tsx +470 -0
  209. package/recce-source/js/src/components/query/QueryForm.tsx +80 -0
  210. package/recce-source/js/src/components/query/QueryPage.tsx +282 -0
  211. package/recce-source/js/src/components/query/QueryResultView.tsx +180 -0
  212. package/recce-source/js/src/components/query/SetupConnectionGuide.tsx +57 -0
  213. package/recce-source/js/src/components/query/SqlEditor.tsx +245 -0
  214. package/recce-source/js/src/components/query/ToggleSwitch.tsx +84 -0
  215. package/recce-source/js/src/components/query/styles.css +21 -0
  216. package/recce-source/js/src/components/routing/DirectUrlAccess.test.tsx +428 -0
  217. package/recce-source/js/src/components/routing/LineageStatePreservation.test.tsx +311 -0
  218. package/recce-source/js/src/components/routing/Navigation.test.tsx +256 -0
  219. package/recce-source/js/src/components/rowcount/RowCountDiffResultView.tsx +109 -0
  220. package/recce-source/js/src/components/rowcount/delta.ts +11 -0
  221. package/recce-source/js/src/components/run/RunList.tsx +303 -0
  222. package/recce-source/js/src/components/run/RunModal.tsx +191 -0
  223. package/recce-source/js/src/components/run/RunPage.tsx +26 -0
  224. package/recce-source/js/src/components/run/RunResultPane.tsx +454 -0
  225. package/recce-source/js/src/components/run/RunStatusAndDate.tsx +106 -0
  226. package/recce-source/js/src/components/run/RunToolbar.tsx +70 -0
  227. package/recce-source/js/src/components/run/RunView.tsx +196 -0
  228. package/recce-source/js/src/components/run/registry.ts +214 -0
  229. package/recce-source/js/src/components/run/types.ts +14 -0
  230. package/recce-source/js/src/components/schema/ColumnNameCell.test.tsx +169 -0
  231. package/recce-source/js/src/components/schema/ColumnNameCell.tsx +198 -0
  232. package/recce-source/js/src/components/schema/SchemaView.tsx +337 -0
  233. package/recce-source/js/src/components/schema/schemaDiff.ts +32 -0
  234. package/recce-source/js/src/components/schema/style.css +134 -0
  235. package/recce-source/js/src/components/screenshot/ScreenshotBox.tsx +39 -0
  236. package/recce-source/js/src/components/shared/HistoryToggle.tsx +35 -0
  237. package/recce-source/js/src/components/split/Split.tsx +40 -0
  238. package/recce-source/js/src/components/split/styles.css +24 -0
  239. package/recce-source/js/src/components/summary/ChangeSummary.tsx +264 -0
  240. package/recce-source/js/src/components/summary/SchemaSummary.tsx +123 -0
  241. package/recce-source/js/src/components/summary/SummaryView.tsx +29 -0
  242. package/recce-source/js/src/components/timeout/IdleTimeoutBadge.tsx +48 -0
  243. package/recce-source/js/src/components/top-k/TopKDiffForm.tsx +58 -0
  244. package/recce-source/js/src/components/top-k/TopKDiffResultView.tsx +73 -0
  245. package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnGroupHeader.tsx +228 -0
  246. package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnHeader.tsx +113 -0
  247. package/recce-source/js/src/components/ui/dataGrid/defaultRenderCell.tsx +72 -0
  248. package/recce-source/js/src/components/ui/dataGrid/index.ts +23 -0
  249. package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.test.tsx +607 -0
  250. package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.tsx +211 -0
  251. package/recce-source/js/src/components/ui/dataGrid/schemaCells.test.tsx +452 -0
  252. package/recce-source/js/src/components/ui/dataGrid/schemaCells.tsx +142 -0
  253. package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.test.tsx +178 -0
  254. package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.tsx +275 -0
  255. package/recce-source/js/src/components/ui/markdown/ExternalLinkConfirmDialog.tsx +134 -0
  256. package/recce-source/js/src/components/ui/markdown/MarkdownContent.tsx +364 -0
  257. package/recce-source/js/src/components/ui/mui/index.ts +13 -0
  258. package/recce-source/js/src/components/ui/mui-provider.tsx +67 -0
  259. package/recce-source/js/src/components/ui/mui-theme.ts +1039 -0
  260. package/recce-source/js/src/components/ui/mui-utils.ts +113 -0
  261. package/recce-source/js/src/components/ui/toaster.tsx +288 -0
  262. package/recce-source/js/src/components/valuediff/ValueDiffDetailResultView.tsx +217 -0
  263. package/recce-source/js/src/components/valuediff/ValueDiffForm.tsx +246 -0
  264. package/recce-source/js/src/components/valuediff/ValueDiffResultView.tsx +82 -0
  265. package/recce-source/js/src/components/valuediff/shared.ts +33 -0
  266. package/recce-source/js/src/constants/tooltipMessage.ts +3 -0
  267. package/recce-source/js/src/constants/urls.ts +1 -0
  268. package/recce-source/js/src/lib/UrlHash.ts +12 -0
  269. package/recce-source/js/src/lib/api/adhocQuery.ts +70 -0
  270. package/recce-source/js/src/lib/api/axiosClient.ts +9 -0
  271. package/recce-source/js/src/lib/api/cacheKeys.ts +13 -0
  272. package/recce-source/js/src/lib/api/checkEvents.ts +252 -0
  273. package/recce-source/js/src/lib/api/checks.ts +129 -0
  274. package/recce-source/js/src/lib/api/cll.ts +53 -0
  275. package/recce-source/js/src/lib/api/connectToCloud.ts +13 -0
  276. package/recce-source/js/src/lib/api/flag.ts +37 -0
  277. package/recce-source/js/src/lib/api/info.ts +198 -0
  278. package/recce-source/js/src/lib/api/instanceInfo.ts +25 -0
  279. package/recce-source/js/src/lib/api/keepAlive.ts +108 -0
  280. package/recce-source/js/src/lib/api/lineagecheck.ts +35 -0
  281. package/recce-source/js/src/lib/api/localStorageKeys.ts +7 -0
  282. package/recce-source/js/src/lib/api/models.ts +59 -0
  283. package/recce-source/js/src/lib/api/profile.ts +65 -0
  284. package/recce-source/js/src/lib/api/rowcount.ts +19 -0
  285. package/recce-source/js/src/lib/api/runs.ts +174 -0
  286. package/recce-source/js/src/lib/api/schemacheck.ts +31 -0
  287. package/recce-source/js/src/lib/api/select.ts +25 -0
  288. package/recce-source/js/src/lib/api/sessionStorageKeys.ts +8 -0
  289. package/recce-source/js/src/lib/api/state.ts +117 -0
  290. package/recce-source/js/src/lib/api/track.ts +281 -0
  291. package/recce-source/js/src/lib/api/types.ts +284 -0
  292. package/recce-source/js/src/lib/api/user.ts +42 -0
  293. package/recce-source/js/src/lib/api/valuediff.ts +46 -0
  294. package/recce-source/js/src/lib/api/version.ts +40 -0
  295. package/recce-source/js/src/lib/const.ts +9 -0
  296. package/recce-source/js/src/lib/dataGrid/crossFunctionConsistency.test.ts +626 -0
  297. package/recce-source/js/src/lib/dataGrid/dataGridFactory.test.ts +2140 -0
  298. package/recce-source/js/src/lib/dataGrid/dataGridFactory.ts +397 -0
  299. package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.test.ts +132 -0
  300. package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.ts +126 -0
  301. package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.test.ts +1627 -0
  302. package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.ts +140 -0
  303. package/recce-source/js/src/lib/dataGrid/generators/toDataGrid.ts +67 -0
  304. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.test.ts +142 -0
  305. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.ts +71 -0
  306. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.test.ts +258 -0
  307. package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.ts +153 -0
  308. package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.test.ts +951 -0
  309. package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.ts +221 -0
  310. package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.test.ts +395 -0
  311. package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.ts +184 -0
  312. package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.test.ts +884 -0
  313. package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.ts +113 -0
  314. package/recce-source/js/src/lib/dataGrid/index.ts +51 -0
  315. package/recce-source/js/src/lib/dataGrid/propertyBased.test.ts +858 -0
  316. package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.test.ts +482 -0
  317. package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.ts +345 -0
  318. package/recce-source/js/src/lib/dataGrid/shared/dataTypeEdgeCases.test.ts +698 -0
  319. package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.test.tsx +820 -0
  320. package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.tsx +277 -0
  321. package/recce-source/js/src/lib/dataGrid/shared/gridUtils.test.ts +785 -0
  322. package/recce-source/js/src/lib/dataGrid/shared/gridUtils.ts +370 -0
  323. package/recce-source/js/src/lib/dataGrid/shared/index.ts +81 -0
  324. package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.test.ts +909 -0
  325. package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.ts +325 -0
  326. package/recce-source/js/src/lib/dataGrid/shared/simpleColumnBuilder.tsx +240 -0
  327. package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.test.tsx +719 -0
  328. package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.tsx +231 -0
  329. package/recce-source/js/src/lib/dataGrid/shared/validation.test.ts +559 -0
  330. package/recce-source/js/src/lib/dataGrid/shared/validation.ts +367 -0
  331. package/recce-source/js/src/lib/dataGrid/warehouseNamingConventions.test.ts +1117 -0
  332. package/recce-source/js/src/lib/formatSelect.ts +50 -0
  333. package/recce-source/js/src/lib/hooks/ApiConfigContext.tsx +181 -0
  334. package/recce-source/js/src/lib/hooks/IdleTimeoutContext.tsx +177 -0
  335. package/recce-source/js/src/lib/hooks/LineageGraphContext.tsx +512 -0
  336. package/recce-source/js/src/lib/hooks/RecceActionContext.tsx +269 -0
  337. package/recce-source/js/src/lib/hooks/RecceCheckContext.tsx +33 -0
  338. package/recce-source/js/src/lib/hooks/RecceContextProvider.tsx +54 -0
  339. package/recce-source/js/src/lib/hooks/RecceInstanceContext.tsx +129 -0
  340. package/recce-source/js/src/lib/hooks/RecceQueryContext.tsx +98 -0
  341. package/recce-source/js/src/lib/hooks/RecceShareStateContext.tsx +59 -0
  342. package/recce-source/js/src/lib/hooks/ScreenShot.tsx +399 -0
  343. package/recce-source/js/src/lib/hooks/useAppRouter.test.ts +211 -0
  344. package/recce-source/js/src/lib/hooks/useAppRouter.ts +200 -0
  345. package/recce-source/js/src/lib/hooks/useCheckEvents.ts +99 -0
  346. package/recce-source/js/src/lib/hooks/useCheckToast.tsx +14 -0
  347. package/recce-source/js/src/lib/hooks/useClipBoardToast.tsx +27 -0
  348. package/recce-source/js/src/lib/hooks/useCountdownToast.tsx +102 -0
  349. package/recce-source/js/src/lib/hooks/useFeedbackCollectionToast.tsx +130 -0
  350. package/recce-source/js/src/lib/hooks/useGuideToast.tsx +45 -0
  351. package/recce-source/js/src/lib/hooks/useIdleDetection.tsx +185 -0
  352. package/recce-source/js/src/lib/hooks/useModelColumns.tsx +113 -0
  353. package/recce-source/js/src/lib/hooks/useRecceInstanceInfo.tsx +13 -0
  354. package/recce-source/js/src/lib/hooks/useRecceServerFlag.tsx +13 -0
  355. package/recce-source/js/src/lib/hooks/useRun.tsx +89 -0
  356. package/recce-source/js/src/lib/hooks/useThemeColors.ts +115 -0
  357. package/recce-source/js/src/lib/mergeKeys.test.ts +89 -0
  358. package/recce-source/js/src/lib/mergeKeys.ts +86 -0
  359. package/recce-source/js/src/lib/result/ResultErrorFallback.tsx +9 -0
  360. package/recce-source/js/src/lib/utils/formatTime.ts +84 -0
  361. package/recce-source/js/src/lib/utils/urls.ts +16 -0
  362. package/recce-source/js/src/utils/DropdownValuesInput.tsx +297 -0
  363. package/recce-source/js/src/utils/formatters.tsx +237 -0
  364. package/recce-source/js/src/utils/transforms.ts +81 -0
  365. package/recce-source/js/tsconfig.json +47 -0
  366. package/recce-source/macros/README.md +8 -0
  367. package/recce-source/macros/recce_athena.sql +73 -0
  368. package/recce-source/pyproject.toml +109 -0
  369. package/recce-source/recce/VERSION +1 -0
  370. package/recce-source/recce/__init__.py +84 -0
  371. package/recce-source/recce/adapter/__init__.py +0 -0
  372. package/recce-source/recce/adapter/base.py +109 -0
  373. package/recce-source/recce/adapter/dbt_adapter/__init__.py +1699 -0
  374. package/recce-source/recce/adapter/dbt_adapter/dbt_version.py +42 -0
  375. package/recce-source/recce/adapter/sqlmesh_adapter.py +141 -0
  376. package/recce-source/recce/apis/__init__.py +0 -0
  377. package/recce-source/recce/apis/check_api.py +203 -0
  378. package/recce-source/recce/apis/check_events_api.py +353 -0
  379. package/recce-source/recce/apis/check_func.py +130 -0
  380. package/recce-source/recce/apis/run_api.py +130 -0
  381. package/recce-source/recce/apis/run_func.py +258 -0
  382. package/recce-source/recce/artifact.py +266 -0
  383. package/recce-source/recce/cli.py +1846 -0
  384. package/recce-source/recce/config.py +127 -0
  385. package/recce-source/recce/connect_to_cloud.py +138 -0
  386. package/recce-source/recce/core.py +334 -0
  387. package/recce-source/recce/diff.py +26 -0
  388. package/recce-source/recce/event/CONFIG +1 -0
  389. package/recce-source/recce/event/SENTRY_DNS +1 -0
  390. package/recce-source/recce/event/__init__.py +304 -0
  391. package/recce-source/recce/event/collector.py +184 -0
  392. package/recce-source/recce/event/track.py +158 -0
  393. package/recce-source/recce/exceptions.py +21 -0
  394. package/recce-source/recce/git.py +77 -0
  395. package/recce-source/recce/github.py +222 -0
  396. package/recce-source/recce/mcp_server.py +861 -0
  397. package/recce-source/recce/models/__init__.py +6 -0
  398. package/recce-source/recce/models/check.py +473 -0
  399. package/recce-source/recce/models/run.py +46 -0
  400. package/recce-source/recce/models/types.py +218 -0
  401. package/recce-source/recce/pull_request.py +124 -0
  402. package/recce-source/recce/run.py +390 -0
  403. package/recce-source/recce/server.py +877 -0
  404. package/recce-source/recce/state/__init__.py +31 -0
  405. package/recce-source/recce/state/cloud.py +644 -0
  406. package/recce-source/recce/state/const.py +26 -0
  407. package/recce-source/recce/state/local.py +56 -0
  408. package/recce-source/recce/state/state.py +119 -0
  409. package/recce-source/recce/state/state_loader.py +174 -0
  410. package/recce-source/recce/summary.py +575 -0
  411. package/recce-source/recce/tasks/__init__.py +23 -0
  412. package/recce-source/recce/tasks/core.py +134 -0
  413. package/recce-source/recce/tasks/dataframe.py +170 -0
  414. package/recce-source/recce/tasks/histogram.py +433 -0
  415. package/recce-source/recce/tasks/lineage.py +19 -0
  416. package/recce-source/recce/tasks/profile.py +298 -0
  417. package/recce-source/recce/tasks/query.py +450 -0
  418. package/recce-source/recce/tasks/rowcount.py +277 -0
  419. package/recce-source/recce/tasks/schema.py +65 -0
  420. package/recce-source/recce/tasks/top_k.py +172 -0
  421. package/recce-source/recce/tasks/utils.py +147 -0
  422. package/recce-source/recce/tasks/valuediff.py +497 -0
  423. package/recce-source/recce/util/__init__.py +4 -0
  424. package/recce-source/recce/util/api_token.py +80 -0
  425. package/recce-source/recce/util/breaking.py +330 -0
  426. package/recce-source/recce/util/cache.py +25 -0
  427. package/recce-source/recce/util/cll.py +355 -0
  428. package/recce-source/recce/util/cloud/__init__.py +15 -0
  429. package/recce-source/recce/util/cloud/base.py +115 -0
  430. package/recce-source/recce/util/cloud/check_events.py +190 -0
  431. package/recce-source/recce/util/cloud/checks.py +242 -0
  432. package/recce-source/recce/util/io.py +120 -0
  433. package/recce-source/recce/util/lineage.py +83 -0
  434. package/recce-source/recce/util/logger.py +25 -0
  435. package/recce-source/recce/util/onboarding_state.py +45 -0
  436. package/recce-source/recce/util/perf_tracking.py +85 -0
  437. package/recce-source/recce/util/pydantic_model.py +22 -0
  438. package/recce-source/recce/util/recce_cloud.py +454 -0
  439. package/recce-source/recce/util/singleton.py +18 -0
  440. package/recce-source/recce/util/startup_perf.py +121 -0
  441. package/recce-source/recce/yaml/__init__.py +58 -0
  442. package/recce-source/recce_cloud/README.md +780 -0
  443. package/recce-source/recce_cloud/VERSION +1 -0
  444. package/recce-source/recce_cloud/__init__.py +24 -0
  445. package/recce-source/recce_cloud/api/__init__.py +17 -0
  446. package/recce-source/recce_cloud/api/base.py +132 -0
  447. package/recce-source/recce_cloud/api/client.py +186 -0
  448. package/recce-source/recce_cloud/api/exceptions.py +26 -0
  449. package/recce-source/recce_cloud/api/factory.py +63 -0
  450. package/recce-source/recce_cloud/api/github.py +106 -0
  451. package/recce-source/recce_cloud/api/gitlab.py +111 -0
  452. package/recce-source/recce_cloud/artifact.py +57 -0
  453. package/recce-source/recce_cloud/ci_providers/__init__.py +9 -0
  454. package/recce-source/recce_cloud/ci_providers/base.py +82 -0
  455. package/recce-source/recce_cloud/ci_providers/detector.py +147 -0
  456. package/recce-source/recce_cloud/ci_providers/github_actions.py +136 -0
  457. package/recce-source/recce_cloud/ci_providers/gitlab_ci.py +130 -0
  458. package/recce-source/recce_cloud/cli.py +434 -0
  459. package/recce-source/recce_cloud/download.py +230 -0
  460. package/recce-source/recce_cloud/hatch_build.py +20 -0
  461. package/recce-source/recce_cloud/pyproject.toml +49 -0
  462. package/recce-source/recce_cloud/upload.py +214 -0
  463. package/recce-source/test.py +0 -0
  464. package/recce-source/tests/__init__.py +0 -0
  465. package/recce-source/tests/adapter/__init__.py +0 -0
  466. package/recce-source/tests/adapter/dbt_adapter/__init__.py +0 -0
  467. package/recce-source/tests/adapter/dbt_adapter/conftest.py +17 -0
  468. package/recce-source/tests/adapter/dbt_adapter/dbt_test_helper.py +298 -0
  469. package/recce-source/tests/adapter/dbt_adapter/test_dbt_adapter.py +25 -0
  470. package/recce-source/tests/adapter/dbt_adapter/test_dbt_cll.py +717 -0
  471. package/recce-source/tests/adapter/dbt_adapter/test_proj/dbt_project.yml +4 -0
  472. package/recce-source/tests/adapter/dbt_adapter/test_proj/manifest.json +1 -0
  473. package/recce-source/tests/adapter/dbt_adapter/test_proj/package-lock.yml +8 -0
  474. package/recce-source/tests/adapter/dbt_adapter/test_proj/packages.yml +7 -0
  475. package/recce-source/tests/adapter/dbt_adapter/test_proj/profiles.yml +6 -0
  476. package/recce-source/tests/adapter/dbt_adapter/test_selector.py +205 -0
  477. package/recce-source/tests/apis/__init__.py +0 -0
  478. package/recce-source/tests/apis/row_count_diff.json +59 -0
  479. package/recce-source/tests/apis/test_check_events_api.py +615 -0
  480. package/recce-source/tests/apis/test_run_func.py +433 -0
  481. package/recce-source/tests/catalog.json +527 -0
  482. package/recce-source/tests/data/manifest/base/catalog.json +1 -0
  483. package/recce-source/tests/data/manifest/base/manifest.json +1 -0
  484. package/recce-source/tests/data/manifest/pr2/catalog.json +1 -0
  485. package/recce-source/tests/data/manifest/pr2/manifest.json +1 -0
  486. package/recce-source/tests/manifest.json +10655 -0
  487. package/recce-source/tests/models/__init__.py +0 -0
  488. package/recce-source/tests/models/test_check.py +731 -0
  489. package/recce-source/tests/models/test_run_models.py +295 -0
  490. package/recce-source/tests/recce_cloud/__init__.py +0 -0
  491. package/recce-source/tests/recce_cloud/test_ci_providers.py +351 -0
  492. package/recce-source/tests/recce_cloud/test_cli.py +735 -0
  493. package/recce-source/tests/recce_cloud/test_client.py +379 -0
  494. package/recce-source/tests/recce_cloud/test_platform_clients.py +483 -0
  495. package/recce-source/tests/recce_state.json +1 -0
  496. package/recce-source/tests/state/test_cloud.py +719 -0
  497. package/recce-source/tests/state/test_local.py +164 -0
  498. package/recce-source/tests/state/test_state_loader.py +211 -0
  499. package/recce-source/tests/tasks/__init__.py +0 -0
  500. package/recce-source/tests/tasks/conftest.py +4 -0
  501. package/recce-source/tests/tasks/test_histogram.py +129 -0
  502. package/recce-source/tests/tasks/test_lineage.py +55 -0
  503. package/recce-source/tests/tasks/test_preset_checks.py +64 -0
  504. package/recce-source/tests/tasks/test_profile.py +397 -0
  505. package/recce-source/tests/tasks/test_query.py +528 -0
  506. package/recce-source/tests/tasks/test_row_count.py +133 -0
  507. package/recce-source/tests/tasks/test_schema.py +122 -0
  508. package/recce-source/tests/tasks/test_top_k.py +77 -0
  509. package/recce-source/tests/tasks/test_utils.py +439 -0
  510. package/recce-source/tests/tasks/test_valuediff.py +361 -0
  511. package/recce-source/tests/test_cli.py +236 -0
  512. package/recce-source/tests/test_cli_mcp_optional.py +45 -0
  513. package/recce-source/tests/test_cloud_listing_cli.py +324 -0
  514. package/recce-source/tests/test_config.py +43 -0
  515. package/recce-source/tests/test_connect_to_cloud.py +82 -0
  516. package/recce-source/tests/test_core.py +174 -0
  517. package/recce-source/tests/test_dbt.py +36 -0
  518. package/recce-source/tests/test_mcp_server.py +505 -0
  519. package/recce-source/tests/test_pull_request.py +130 -0
  520. package/recce-source/tests/test_server.py +202 -0
  521. package/recce-source/tests/test_server_lifespan.py +138 -0
  522. package/recce-source/tests/test_summary.py +73 -0
  523. package/recce-source/tests/util/__init__.py +0 -0
  524. package/recce-source/tests/util/cloud/__init__.py +0 -0
  525. package/recce-source/tests/util/cloud/test_check_events.py +255 -0
  526. package/recce-source/tests/util/cloud/test_checks.py +204 -0
  527. package/recce-source/tests/util/test_api_token.py +119 -0
  528. package/recce-source/tests/util/test_breaking.py +1427 -0
  529. package/recce-source/tests/util/test_cll.py +706 -0
  530. package/recce-source/tests/util/test_lineage.py +122 -0
  531. package/recce-source/tests/util/test_onboarding_state.py +84 -0
  532. package/recce-source/tests/util/test_recce_cloud.py +231 -0
  533. package/recce-source/tox.ini +40 -0
  534. package/recce-source/uv.lock +3928 -0
  535. package/src/api/index.ts +32 -0
  536. package/src/components/index.ts +154 -0
  537. package/src/global.d.ts +14 -0
  538. package/src/hooks/index.ts +56 -0
  539. package/src/index.ts +17 -0
  540. package/src/lib/hooks/RouteConfigContext.ts +139 -0
  541. package/src/lib/hooks/useAppRouter.ts +240 -0
  542. package/src/mui-augmentation.d.ts +139 -0
  543. package/src/theme/index.ts +13 -0
  544. package/src/theme.ts +23 -0
  545. package/src/types/index.ts +23 -0
  546. package/dist/index-BNUP2V_N.d.ts.map +0 -1
  547. package/dist/index-DOPZuhD8.d.mts.map +0 -1
  548. package/dist/theme.d.mts.map +0 -1
  549. package/dist/theme.d.ts.map +0 -1
@@ -0,0 +1,735 @@
1
+ """
2
+ Integration tests for recce-cloud CLI commands.
3
+ """
4
+
5
+ import os
6
+ import tempfile
7
+ import unittest
8
+ from pathlib import Path
9
+ from unittest.mock import patch
10
+
11
+ from click.testing import CliRunner
12
+
13
+ from recce_cloud.cli import cloud_cli
14
+
15
+
16
+ class TestUploadDryRun(unittest.TestCase):
17
+ """Test cases for the --dry-run flag in upload command."""
18
+
19
+ def setUp(self):
20
+ """Set up test fixtures."""
21
+ self.runner = CliRunner()
22
+ self.temp_dir = tempfile.mkdtemp()
23
+
24
+ # Create mock dbt artifacts
25
+ manifest_path = Path(self.temp_dir) / "manifest.json"
26
+ catalog_path = Path(self.temp_dir) / "catalog.json"
27
+
28
+ manifest_content = {
29
+ "metadata": {"adapter_type": "postgres"},
30
+ "nodes": {},
31
+ }
32
+
33
+ catalog_content = {
34
+ "nodes": {},
35
+ }
36
+
37
+ import json
38
+
39
+ with open(manifest_path, "w") as f:
40
+ json.dump(manifest_content, f)
41
+
42
+ with open(catalog_path, "w") as f:
43
+ json.dump(catalog_content, f)
44
+
45
+ def tearDown(self):
46
+ """Clean up test fixtures."""
47
+ import shutil
48
+
49
+ if os.path.exists(self.temp_dir):
50
+ shutil.rmtree(self.temp_dir)
51
+
52
+ def test_dry_run_github_actions_pr_context(self):
53
+ """Test dry-run with GitHub Actions PR context."""
54
+ env = {
55
+ "GITHUB_ACTIONS": "true",
56
+ "GITHUB_REPOSITORY": "DataRecce/recce",
57
+ "GITHUB_EVENT_NAME": "pull_request",
58
+ "GITHUB_SHA": "abc123def456",
59
+ "GITHUB_HEAD_REF": "feature/test-branch",
60
+ "GITHUB_BASE_REF": "main",
61
+ "RECCE_API_TOKEN": "test_token_123",
62
+ }
63
+
64
+ # Create mock event file
65
+ event_file = Path(self.temp_dir) / "github_event.json"
66
+ import json
67
+
68
+ with open(event_file, "w") as f:
69
+ json.dump({"pull_request": {"number": 42}}, f)
70
+
71
+ env["GITHUB_EVENT_PATH"] = str(event_file)
72
+
73
+ with patch.dict(os.environ, env, clear=True):
74
+ result = self.runner.invoke(
75
+ cloud_cli,
76
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
77
+ )
78
+
79
+ # Assertions
80
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
81
+ self.assertIn("Dry run mode enabled", result.output)
82
+ self.assertIn("Platform Information:", result.output)
83
+ self.assertIn("Platform: github-actions", result.output)
84
+ self.assertIn("Repository: DataRecce/recce", result.output)
85
+ self.assertIn("CR Number: 42", result.output)
86
+ self.assertIn("Commit SHA: abc123de", result.output)
87
+ self.assertIn("Source Branch: feature/test-branch", result.output)
88
+ self.assertIn("Base Branch: main", result.output)
89
+ self.assertIn("Upload Workflow:", result.output)
90
+ self.assertIn("Auto-create session and upload", result.output)
91
+ self.assertIn("Platform-specific APIs will be used", result.output)
92
+ self.assertIn("Files to upload:", result.output)
93
+ self.assertIn("manifest.json:", result.output)
94
+ self.assertIn("catalog.json:", result.output)
95
+ self.assertIn("Adapter type: postgres", result.output)
96
+ self.assertIn("Dry run completed successfully", result.output)
97
+
98
+ def test_dry_run_gitlab_ci_mr_context(self):
99
+ """Test dry-run with GitLab CI MR context."""
100
+ env = {
101
+ "GITLAB_CI": "true",
102
+ "CI_PROJECT_PATH": "recce/jaffle-shop",
103
+ "CI_PROJECT_URL": "https://gitlab.com/recce/jaffle-shop",
104
+ "CI_MERGE_REQUEST_IID": "5",
105
+ "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME": "feature/new-models",
106
+ "CI_MERGE_REQUEST_TARGET_BRANCH_NAME": "main",
107
+ "CI_COMMIT_SHA": "def456abc789",
108
+ "CI_SERVER_URL": "https://gitlab.com",
109
+ "RECCE_API_TOKEN": "test_token_abc",
110
+ }
111
+
112
+ with patch.dict(os.environ, env, clear=True):
113
+ result = self.runner.invoke(
114
+ cloud_cli,
115
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
116
+ )
117
+
118
+ # Assertions
119
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
120
+ self.assertIn("Dry run mode enabled", result.output)
121
+ self.assertIn("Platform Information:", result.output)
122
+ self.assertIn("Platform: gitlab-ci", result.output)
123
+ self.assertIn("Repository: recce/jaffle-shop", result.output)
124
+ self.assertIn("CR Number: 5", result.output)
125
+ self.assertIn("Commit SHA: def456ab", result.output)
126
+ self.assertIn("Source Branch: feature/new-models", result.output)
127
+ self.assertIn("Base Branch: main", result.output)
128
+ self.assertIn("Auto-create session and upload", result.output)
129
+ self.assertIn("Platform-specific APIs will be used", result.output)
130
+ self.assertIn("Adapter type: postgres", result.output)
131
+
132
+ def test_dry_run_gitlab_ci_self_hosted(self):
133
+ """Test dry-run with self-hosted GitLab instance."""
134
+ env = {
135
+ "GITLAB_CI": "true",
136
+ "CI_PROJECT_PATH": "data-team/dbt-project",
137
+ "CI_PROJECT_URL": "https://gitlab.mycompany.com/data-team/dbt-project",
138
+ "CI_MERGE_REQUEST_IID": "25",
139
+ "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME": "develop",
140
+ "CI_MERGE_REQUEST_TARGET_BRANCH_NAME": "production",
141
+ "CI_COMMIT_SHA": "fedcba987654",
142
+ "CI_SERVER_URL": "https://gitlab.mycompany.com",
143
+ "RECCE_API_TOKEN": "test_token_xyz",
144
+ }
145
+
146
+ with patch.dict(os.environ, env, clear=True):
147
+ result = self.runner.invoke(
148
+ cloud_cli,
149
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
150
+ )
151
+
152
+ # Assertions
153
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
154
+ self.assertIn("Platform: gitlab-ci", result.output)
155
+ self.assertIn("Repository: data-team/dbt-project", result.output)
156
+ self.assertIn("CR Number: 25", result.output)
157
+ self.assertIn("Source Branch: develop", result.output)
158
+ self.assertIn("Base Branch: production", result.output)
159
+
160
+ def test_dry_run_with_session_id(self):
161
+ """Test dry-run with existing session ID (generic workflow)."""
162
+ env = {
163
+ "GITHUB_ACTIONS": "true",
164
+ "GITHUB_REPOSITORY": "DataRecce/recce",
165
+ "RECCE_API_TOKEN": "test_token_789",
166
+ }
167
+
168
+ with patch.dict(os.environ, env, clear=True):
169
+ result = self.runner.invoke(
170
+ cloud_cli,
171
+ [
172
+ "upload",
173
+ "--target-path",
174
+ self.temp_dir,
175
+ "--session-id",
176
+ "sess_abc123xyz",
177
+ "--dry-run",
178
+ ],
179
+ )
180
+
181
+ # Assertions
182
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
183
+ self.assertIn("Dry run mode enabled", result.output)
184
+ self.assertIn("Upload Workflow:", result.output)
185
+ self.assertIn("Upload to existing session", result.output)
186
+ self.assertIn("Session ID: sess_abc123xyz", result.output)
187
+ self.assertNotIn("Auto-create session", result.output)
188
+
189
+ def test_dry_run_github_main_branch(self):
190
+ """Test dry-run with GitHub Actions main branch (no PR)."""
191
+ env = {
192
+ "GITHUB_ACTIONS": "true",
193
+ "GITHUB_REPOSITORY": "DataRecce/recce",
194
+ "GITHUB_EVENT_NAME": "push",
195
+ "GITHUB_REF": "refs/heads/main",
196
+ "GITHUB_SHA": "xyz789abc123",
197
+ "RECCE_API_TOKEN": "test_token_456",
198
+ }
199
+
200
+ with patch.dict(os.environ, env, clear=True):
201
+ result = self.runner.invoke(
202
+ cloud_cli,
203
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
204
+ )
205
+
206
+ # Assertions
207
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
208
+ self.assertIn("Platform: github-actions", result.output)
209
+ self.assertIn("Repository: DataRecce/recce", result.output)
210
+ self.assertIn("Commit SHA: xyz789ab", result.output)
211
+ # Session type depends on git branch detection, could be prod or dev
212
+ self.assertIn("Session Type:", result.output)
213
+ # Should not have CR number
214
+ self.assertNotIn("CR Number:", result.output)
215
+
216
+ def test_dry_run_gitlab_main_branch(self):
217
+ """Test dry-run with GitLab CI main branch (no MR)."""
218
+ env = {
219
+ "GITLAB_CI": "true",
220
+ "CI_PROJECT_PATH": "recce/analytics",
221
+ "CI_PROJECT_URL": "https://gitlab.com/recce/analytics",
222
+ "CI_COMMIT_BRANCH": "main",
223
+ "CI_COMMIT_SHA": "123abc456def",
224
+ "CI_SERVER_URL": "https://gitlab.com",
225
+ "RECCE_API_TOKEN": "test_token_main",
226
+ }
227
+
228
+ with patch.dict(os.environ, env, clear=True):
229
+ result = self.runner.invoke(
230
+ cloud_cli,
231
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
232
+ )
233
+
234
+ # Assertions
235
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
236
+ self.assertIn("Platform: gitlab-ci", result.output)
237
+ self.assertIn("Repository: recce/analytics", result.output)
238
+ # Session type depends on git branch detection, could be prod or dev
239
+ self.assertIn("Session Type:", result.output)
240
+ # Should not have CR number
241
+ self.assertNotIn("CR Number:", result.output)
242
+
243
+ def test_dry_run_with_manual_overrides(self):
244
+ """Test dry-run with manual overrides."""
245
+ env = {
246
+ "GITHUB_ACTIONS": "true",
247
+ "GITHUB_REPOSITORY": "DataRecce/recce",
248
+ "GITHUB_EVENT_NAME": "pull_request",
249
+ "GITHUB_SHA": "abc123",
250
+ "RECCE_API_TOKEN": "test_token",
251
+ }
252
+
253
+ # Create mock event file with PR number 42
254
+ event_file = Path(self.temp_dir) / "github_event.json"
255
+ import json
256
+
257
+ with open(event_file, "w") as f:
258
+ json.dump({"pull_request": {"number": 42}}, f)
259
+
260
+ env["GITHUB_EVENT_PATH"] = str(event_file)
261
+
262
+ with patch.dict(os.environ, env, clear=True):
263
+ result = self.runner.invoke(
264
+ cloud_cli,
265
+ [
266
+ "upload",
267
+ "--target-path",
268
+ self.temp_dir,
269
+ "--cr",
270
+ "100",
271
+ "--type",
272
+ "cr",
273
+ "--dry-run",
274
+ ],
275
+ )
276
+
277
+ # Assertions
278
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
279
+ # Should show overridden CR number
280
+ self.assertIn("CR Number: 100", result.output)
281
+ self.assertIn("Session Type: cr", result.output)
282
+
283
+ def test_dry_run_no_ci_environment(self):
284
+ """Test dry-run without CI environment (local development)."""
285
+ env = {
286
+ "RECCE_API_TOKEN": "test_token_local",
287
+ }
288
+
289
+ with patch.dict(os.environ, env, clear=True):
290
+ result = self.runner.invoke(
291
+ cloud_cli,
292
+ [
293
+ "upload",
294
+ "--target-path",
295
+ self.temp_dir,
296
+ "--session-id",
297
+ "sess_local123",
298
+ "--dry-run",
299
+ ],
300
+ )
301
+
302
+ # Assertions
303
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
304
+ self.assertIn("Dry run mode enabled", result.output)
305
+ self.assertIn("Upload to existing session", result.output)
306
+ self.assertIn("Session ID: sess_local123", result.output)
307
+ # Should not show platform information
308
+ self.assertNotIn("Platform Information:", result.output)
309
+
310
+ def test_dry_run_unsupported_platform_without_session_id(self):
311
+ """Test dry-run with unsupported platform and no session ID."""
312
+ env = {
313
+ "RECCE_API_TOKEN": "test_token",
314
+ }
315
+
316
+ with patch.dict(os.environ, env, clear=True):
317
+ result = self.runner.invoke(
318
+ cloud_cli,
319
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
320
+ )
321
+
322
+ # Assertions
323
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
324
+ self.assertIn("Dry run mode enabled", result.output)
325
+ self.assertIn("Auto-create session and upload", result.output)
326
+ self.assertIn("Warning: Platform not supported for auto-session creation", result.output)
327
+
328
+ def test_dry_run_missing_artifacts(self):
329
+ """Test dry-run with missing dbt artifacts."""
330
+ import shutil
331
+
332
+ shutil.rmtree(self.temp_dir)
333
+
334
+ env = {
335
+ "GITHUB_ACTIONS": "true",
336
+ "RECCE_API_TOKEN": "test_token",
337
+ }
338
+
339
+ with patch.dict(os.environ, env, clear=True):
340
+ result = self.runner.invoke(
341
+ cloud_cli,
342
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
343
+ )
344
+
345
+ # Assertions
346
+ # Should fail before dry-run validation happens
347
+ self.assertNotEqual(result.exit_code, 0)
348
+ self.assertIn("does not exist", result.output)
349
+
350
+ def test_dry_run_custom_target_path(self):
351
+ """Test dry-run with custom target path."""
352
+ env = {
353
+ "GITHUB_ACTIONS": "true",
354
+ "GITHUB_REPOSITORY": "DataRecce/recce",
355
+ "RECCE_API_TOKEN": "test_token",
356
+ }
357
+
358
+ with patch.dict(os.environ, env, clear=True):
359
+ result = self.runner.invoke(
360
+ cloud_cli,
361
+ ["upload", "--target-path", self.temp_dir, "--dry-run"],
362
+ )
363
+
364
+ # Assertions
365
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
366
+ self.assertIn(self.temp_dir, result.output)
367
+ self.assertIn("manifest.json:", result.output)
368
+ self.assertIn("catalog.json:", result.output)
369
+
370
+
371
+ class TestDownloadDryRun(unittest.TestCase):
372
+ """Test cases for the --dry-run flag in download command."""
373
+
374
+ def setUp(self):
375
+ """Set up test fixtures."""
376
+ self.runner = CliRunner()
377
+ self.temp_dir = tempfile.mkdtemp()
378
+
379
+ def tearDown(self):
380
+ """Clean up test fixtures."""
381
+ import shutil
382
+
383
+ if os.path.exists(self.temp_dir):
384
+ shutil.rmtree(self.temp_dir)
385
+
386
+ def test_dry_run_github_actions_pr_context(self):
387
+ """Test dry-run with GitHub Actions PR context."""
388
+ env = {
389
+ "GITHUB_ACTIONS": "true",
390
+ "GITHUB_REPOSITORY": "DataRecce/recce",
391
+ "GITHUB_EVENT_NAME": "pull_request",
392
+ "GITHUB_SHA": "abc123def456",
393
+ "GITHUB_HEAD_REF": "feature/test-branch",
394
+ "GITHUB_BASE_REF": "main",
395
+ "GITHUB_TOKEN": "test_token_123",
396
+ }
397
+
398
+ # Create mock event file
399
+ event_file = Path(self.temp_dir) / "github_event.json"
400
+ import json
401
+
402
+ with open(event_file, "w") as f:
403
+ json.dump({"pull_request": {"number": 42}}, f)
404
+
405
+ env["GITHUB_EVENT_PATH"] = str(event_file)
406
+
407
+ download_dir = Path(self.temp_dir) / "download"
408
+
409
+ with patch.dict(os.environ, env, clear=True):
410
+ result = self.runner.invoke(
411
+ cloud_cli,
412
+ ["download", "--target-path", str(download_dir), "--dry-run"],
413
+ )
414
+
415
+ # Assertions
416
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
417
+ self.assertIn("Dry run mode enabled", result.output)
418
+ self.assertIn("Platform Information:", result.output)
419
+ self.assertIn("Platform: github-actions", result.output)
420
+ self.assertIn("Repository: DataRecce/recce", result.output)
421
+ self.assertIn("Session Type: cr", result.output)
422
+ self.assertIn("PR Number: 42", result.output)
423
+ self.assertIn("Download Workflow:", result.output)
424
+ self.assertIn("Auto-detect and download PR/MR session", result.output)
425
+ self.assertIn("Platform-specific APIs will be used", result.output)
426
+ self.assertIn("Download destination:", result.output)
427
+ self.assertIn("manifest.json, catalog.json", result.output)
428
+ self.assertIn("Dry run completed successfully", result.output)
429
+
430
+ # Download command should NOT show commit SHA or branches (irrelevant for download)
431
+ self.assertNotIn("Commit SHA:", result.output)
432
+ self.assertNotIn("Base Branch:", result.output)
433
+ self.assertNotIn("Source Branch:", result.output)
434
+
435
+ def test_dry_run_gitlab_ci_mr_context(self):
436
+ """Test dry-run with GitLab CI MR context."""
437
+ env = {
438
+ "GITLAB_CI": "true",
439
+ "CI_PROJECT_PATH": "recce/jaffle-shop",
440
+ "CI_PROJECT_URL": "https://gitlab.com/recce/jaffle-shop",
441
+ "CI_MERGE_REQUEST_IID": "5",
442
+ "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME": "feature/new-models",
443
+ "CI_MERGE_REQUEST_TARGET_BRANCH_NAME": "main",
444
+ "CI_COMMIT_SHA": "def456abc789",
445
+ "CI_SERVER_URL": "https://gitlab.com",
446
+ "CI_JOB_TOKEN": "test_job_token_abc",
447
+ }
448
+
449
+ download_dir = Path(self.temp_dir) / "download"
450
+
451
+ with patch.dict(os.environ, env, clear=True):
452
+ result = self.runner.invoke(
453
+ cloud_cli,
454
+ ["download", "--target-path", str(download_dir), "--dry-run"],
455
+ )
456
+
457
+ # Assertions
458
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
459
+ self.assertIn("Dry run mode enabled", result.output)
460
+ self.assertIn("Platform Information:", result.output)
461
+ self.assertIn("Platform: gitlab-ci", result.output)
462
+ self.assertIn("Repository: recce/jaffle-shop", result.output)
463
+ self.assertIn("Session Type: cr", result.output)
464
+ self.assertIn("MR Number: 5", result.output)
465
+ self.assertIn("Auto-detect and download PR/MR session", result.output)
466
+ self.assertIn("Platform-specific APIs will be used", result.output)
467
+
468
+ # Download command should NOT show commit SHA or branches (irrelevant for download)
469
+ self.assertNotIn("Commit SHA:", result.output)
470
+ self.assertNotIn("Base Branch:", result.output)
471
+ self.assertNotIn("Source Branch:", result.output)
472
+
473
+ def test_dry_run_with_prod_flag(self):
474
+ """Test dry-run with --prod flag for production/base session."""
475
+ env = {
476
+ "GITHUB_ACTIONS": "true",
477
+ "GITHUB_REPOSITORY": "DataRecce/recce",
478
+ "GITHUB_EVENT_NAME": "pull_request",
479
+ "GITHUB_SHA": "abc123",
480
+ "GITHUB_TOKEN": "test_token",
481
+ }
482
+
483
+ # Create mock event file
484
+ event_file = Path(self.temp_dir) / "github_event.json"
485
+ import json
486
+
487
+ with open(event_file, "w") as f:
488
+ json.dump({"pull_request": {"number": 42}}, f)
489
+
490
+ env["GITHUB_EVENT_PATH"] = str(event_file)
491
+ download_dir = Path(self.temp_dir) / "download"
492
+
493
+ with patch.dict(os.environ, env, clear=True):
494
+ result = self.runner.invoke(
495
+ cloud_cli,
496
+ ["download", "--target-path", str(download_dir), "--prod", "--dry-run"],
497
+ )
498
+
499
+ # Assertions
500
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
501
+ self.assertIn("Dry run mode enabled", result.output)
502
+ self.assertIn("Download project's production/base session", result.output)
503
+ self.assertIn("Session Type: prod", result.output)
504
+
505
+ # Prod session should NOT show PR/CR number
506
+ self.assertNotIn("PR Number:", result.output)
507
+ self.assertNotIn("CR Number:", result.output)
508
+
509
+ # Download command should NOT show commit SHA or branches
510
+ self.assertNotIn("Commit SHA:", result.output)
511
+ self.assertNotIn("Base Branch:", result.output)
512
+ self.assertNotIn("Source Branch:", result.output)
513
+
514
+ def test_dry_run_with_session_id(self):
515
+ """Test dry-run with existing session ID (generic workflow)."""
516
+ env = {
517
+ "RECCE_API_TOKEN": "test_token_789",
518
+ }
519
+
520
+ download_dir = Path(self.temp_dir) / "download"
521
+
522
+ with patch.dict(os.environ, env, clear=True):
523
+ result = self.runner.invoke(
524
+ cloud_cli,
525
+ [
526
+ "download",
527
+ "--target-path",
528
+ str(download_dir),
529
+ "--session-id",
530
+ "sess_abc123xyz",
531
+ "--dry-run",
532
+ ],
533
+ )
534
+
535
+ # Assertions
536
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
537
+ self.assertIn("Dry run mode enabled", result.output)
538
+ self.assertIn("Download Workflow:", result.output)
539
+ self.assertIn("Download from specific session ID", result.output)
540
+ self.assertIn("Session ID: sess_abc123xyz", result.output)
541
+
542
+ def test_dry_run_with_force_flag(self):
543
+ """Test dry-run with --force flag."""
544
+ env = {
545
+ "GITHUB_ACTIONS": "true",
546
+ "GITHUB_REPOSITORY": "DataRecce/recce",
547
+ "GITHUB_TOKEN": "test_token",
548
+ }
549
+
550
+ download_dir = Path(self.temp_dir) / "download"
551
+
552
+ with patch.dict(os.environ, env, clear=True):
553
+ result = self.runner.invoke(
554
+ cloud_cli,
555
+ ["download", "--target-path", str(download_dir), "--force", "--dry-run"],
556
+ )
557
+
558
+ # Assertions
559
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
560
+ self.assertIn("Dry run mode enabled", result.output)
561
+ self.assertIn("Will overwrite existing files", result.output)
562
+
563
+ def test_dry_run_existing_target_path_without_force(self):
564
+ """Test dry-run with existing target path (without --force)."""
565
+ env = {
566
+ "GITHUB_ACTIONS": "true",
567
+ "GITHUB_REPOSITORY": "DataRecce/recce",
568
+ "GITHUB_TOKEN": "test_token",
569
+ }
570
+
571
+ # Create existing target directory
572
+ download_dir = Path(self.temp_dir) / "download"
573
+ download_dir.mkdir()
574
+
575
+ with patch.dict(os.environ, env, clear=True):
576
+ result = self.runner.invoke(
577
+ cloud_cli,
578
+ ["download", "--target-path", str(download_dir), "--dry-run"],
579
+ )
580
+
581
+ # Assertions
582
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
583
+ self.assertIn("Warning: Target path exists (use --force to overwrite)", result.output)
584
+
585
+ def test_dry_run_session_id_with_prod_warning(self):
586
+ """Test dry-run shows warning when both --session-id and --prod are provided."""
587
+ env = {
588
+ "RECCE_API_TOKEN": "test_token",
589
+ }
590
+
591
+ download_dir = Path(self.temp_dir) / "download"
592
+
593
+ with patch.dict(os.environ, env, clear=True):
594
+ result = self.runner.invoke(
595
+ cloud_cli,
596
+ [
597
+ "download",
598
+ "--target-path",
599
+ str(download_dir),
600
+ "--session-id",
601
+ "sess_123",
602
+ "--prod",
603
+ "--dry-run",
604
+ ],
605
+ )
606
+
607
+ # Assertions
608
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
609
+ self.assertIn("Warning:", result.output)
610
+ self.assertIn("--prod is ignored when --session-id is provided", result.output)
611
+
612
+ def test_dry_run_no_ci_without_session_id(self):
613
+ """Test dry-run without CI environment and no session ID."""
614
+ env = {
615
+ "RECCE_API_TOKEN": "test_token",
616
+ }
617
+
618
+ download_dir = Path(self.temp_dir) / "download"
619
+
620
+ with patch.dict(os.environ, env, clear=True):
621
+ result = self.runner.invoke(
622
+ cloud_cli,
623
+ ["download", "--target-path", str(download_dir), "--dry-run"],
624
+ )
625
+
626
+ # Assertions
627
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
628
+ self.assertIn("Dry run mode enabled", result.output)
629
+ self.assertIn("Warning: Platform not supported for auto-session discovery", result.output)
630
+
631
+ def test_dry_run_gitlab_ci_self_hosted(self):
632
+ """Test dry-run with self-hosted GitLab instance."""
633
+ env = {
634
+ "GITLAB_CI": "true",
635
+ "CI_PROJECT_PATH": "data-team/dbt-project",
636
+ "CI_PROJECT_URL": "https://gitlab.mycompany.com/data-team/dbt-project",
637
+ "CI_MERGE_REQUEST_IID": "25",
638
+ "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME": "develop",
639
+ "CI_MERGE_REQUEST_TARGET_BRANCH_NAME": "production",
640
+ "CI_COMMIT_SHA": "fedcba987654",
641
+ "CI_SERVER_URL": "https://gitlab.mycompany.com",
642
+ "CI_JOB_TOKEN": "test_job_token_xyz",
643
+ }
644
+
645
+ download_dir = Path(self.temp_dir) / "download"
646
+
647
+ with patch.dict(os.environ, env, clear=True):
648
+ result = self.runner.invoke(
649
+ cloud_cli,
650
+ ["download", "--target-path", str(download_dir), "--dry-run"],
651
+ )
652
+
653
+ # Assertions
654
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
655
+ self.assertIn("Platform: gitlab-ci", result.output)
656
+ self.assertIn("Repository: data-team/dbt-project", result.output)
657
+ self.assertIn("Session Type: cr", result.output)
658
+ self.assertIn("MR Number: 25", result.output)
659
+
660
+ # Download command should NOT show commit SHA or branches (irrelevant for download)
661
+ self.assertNotIn("Commit SHA:", result.output)
662
+ self.assertNotIn("Base Branch:", result.output)
663
+ self.assertNotIn("Source Branch:", result.output)
664
+
665
+ def test_dry_run_github_main_branch(self):
666
+ """Test dry-run with GitHub Actions main branch (no PR).
667
+
668
+ Without --prod flag, the session type is auto-detected as 'prod' because:
669
+ 1. No PR number (GITHUB_HEAD_REF is not set)
670
+ 2. Branch is 'main' (from git command fallback, mocked in test)
671
+ 3. determine_session_type(cr_number=None, source_branch='main') returns 'prod'
672
+
673
+ This allows download to work without explicit --prod flag on main branch.
674
+ """
675
+ env = {
676
+ "GITHUB_ACTIONS": "true",
677
+ "GITHUB_REPOSITORY": "DataRecce/recce",
678
+ "GITHUB_EVENT_NAME": "push",
679
+ "GITHUB_REF": "refs/heads/main",
680
+ "GITHUB_SHA": "xyz789abc123",
681
+ "GITHUB_TOKEN": "test_token_456",
682
+ }
683
+
684
+ download_dir = Path(self.temp_dir) / "download"
685
+
686
+ # Mock git command to return "main" branch
687
+ # This simulates the fallback when GITHUB_REF_NAME is not available
688
+ with patch.dict(os.environ, env, clear=True):
689
+ with patch("recce_cloud.ci_providers.base.BaseCIProvider.run_git_command") as mock_git:
690
+ mock_git.return_value = "main"
691
+
692
+ result = self.runner.invoke(
693
+ cloud_cli,
694
+ ["download", "--target-path", str(download_dir), "--dry-run"],
695
+ )
696
+
697
+ # Assertions
698
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
699
+ self.assertIn("Platform: github-actions", result.output)
700
+ self.assertIn("Repository: DataRecce/recce", result.output)
701
+ self.assertIn("Session Type: prod", result.output) # Auto-detected from main branch
702
+
703
+ # Prod session should NOT show CR number or PR number
704
+ self.assertNotIn("PR Number:", result.output)
705
+ self.assertNotIn("CR Number:", result.output)
706
+
707
+ # Download command should NOT show commit SHA or branches (irrelevant for download)
708
+ self.assertNotIn("Commit SHA:", result.output)
709
+ self.assertNotIn("Base Branch:", result.output)
710
+ self.assertNotIn("Source Branch:", result.output)
711
+
712
+ def test_dry_run_custom_target_path(self):
713
+ """Test dry-run with custom target path."""
714
+ env = {
715
+ "GITHUB_ACTIONS": "true",
716
+ "GITHUB_REPOSITORY": "DataRecce/recce",
717
+ "GITHUB_TOKEN": "test_token",
718
+ }
719
+
720
+ custom_path = Path(self.temp_dir) / "custom" / "target"
721
+
722
+ with patch.dict(os.environ, env, clear=True):
723
+ result = self.runner.invoke(
724
+ cloud_cli,
725
+ ["download", "--target-path", str(custom_path), "--dry-run"],
726
+ )
727
+
728
+ # Assertions
729
+ self.assertEqual(result.exit_code, 0, f"Command failed: {result.output}")
730
+ self.assertIn(str(custom_path), result.output)
731
+ self.assertIn("manifest.json, catalog.json", result.output)
732
+
733
+
734
+ if __name__ == "__main__":
735
+ unittest.main()