@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.
- package/dist/api.d.mts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/components.d.mts +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/hooks.d.mts +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/{index-BNUP2V_N.d.ts → index-B9lSPJTi.d.ts} +184 -2
- package/dist/index-B9lSPJTi.d.ts.map +1 -0
- package/dist/{index-DOPZuhD8.d.mts → index-IIXVIoOL.d.mts} +253 -71
- package/dist/index-IIXVIoOL.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +4 -0
- package/dist/theme.d.mts +2 -185
- package/dist/theme.d.ts +2 -185
- package/dist/types.d.mts +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +4 -2
- package/recce-source/.editorconfig +26 -0
- package/recce-source/.flake8 +37 -0
- package/recce-source/.github/ISSUE_TEMPLATE/bug_report.yml +67 -0
- package/recce-source/.github/ISSUE_TEMPLATE/custom.md +10 -0
- package/recce-source/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/recce-source/.github/PULL_REQUEST_TEMPLATE.md +21 -0
- package/recce-source/.github/copilot-instructions.md +331 -0
- package/recce-source/.github/instructions/backend-instructions.md +541 -0
- package/recce-source/.github/instructions/frontend-instructions.md +317 -0
- package/recce-source/.github/workflows/build-statics.yaml +72 -0
- package/recce-source/.github/workflows/bump.yaml +48 -0
- package/recce-source/.github/workflows/integration-tests-cloud.yaml +92 -0
- package/recce-source/.github/workflows/integration-tests-sqlmesh.yaml +33 -0
- package/recce-source/.github/workflows/integration-tests.yaml +52 -0
- package/recce-source/.github/workflows/nightly.yaml +246 -0
- package/recce-source/.github/workflows/release.yaml +196 -0
- package/recce-source/.github/workflows/tests-js.yaml +58 -0
- package/recce-source/.github/workflows/tests-python.yaml +128 -0
- package/recce-source/.pre-commit-config.yaml +26 -0
- package/recce-source/CLAUDE.md +483 -0
- package/recce-source/CODE_OF_CONDUCT.md +128 -0
- package/recce-source/CONTRIBUTING.md +107 -0
- package/recce-source/LICENSE +201 -0
- package/recce-source/Makefile +126 -0
- package/recce-source/README.md +182 -0
- package/recce-source/RECCE_CLOUD.md +81 -0
- package/recce-source/SECURITY.md +25 -0
- package/recce-source/docs/PACKAGING.md +340 -0
- package/recce-source/docs/README.md +1 -0
- package/recce-source/integration_tests/dbt/dbt_project.yml +26 -0
- package/recce-source/integration_tests/dbt/models/customers.sql +69 -0
- package/recce-source/integration_tests/dbt/models/docs.md +14 -0
- package/recce-source/integration_tests/dbt/models/orders.sql +56 -0
- package/recce-source/integration_tests/dbt/models/schema.yml +82 -0
- package/recce-source/integration_tests/dbt/models/staging/schema.yml +31 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_customers.sql +22 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_orders.sql +23 -0
- package/recce-source/integration_tests/dbt/models/staging/stg_payments.sql +25 -0
- package/recce-source/integration_tests/dbt/packages.yml +7 -0
- package/recce-source/integration_tests/dbt/profiles.yml +8 -0
- package/recce-source/integration_tests/dbt/seeds/raw_customers.csv +101 -0
- package/recce-source/integration_tests/dbt/seeds/raw_orders.csv +100 -0
- package/recce-source/integration_tests/dbt/seeds/raw_payments.csv +114 -0
- package/recce-source/integration_tests/dbt/seeds/raw_statuses.csv +5 -0
- package/recce-source/integration_tests/dbt/smoke_test.sh +72 -0
- package/recce-source/integration_tests/dbt/smoke_test_cloud.sh +71 -0
- package/recce-source/integration_tests/sqlmesh/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/audits/assert_item_price_above_zero.sql +9 -0
- package/recce-source/integration_tests/sqlmesh/audits/items.sql +7 -0
- package/recce-source/integration_tests/sqlmesh/audits/order_items.sql +7 -0
- package/recce-source/integration_tests/sqlmesh/config.py +171 -0
- package/recce-source/integration_tests/sqlmesh/helper.py +20 -0
- package/recce-source/integration_tests/sqlmesh/hooks/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/macros/__init__.py +0 -0
- package/recce-source/integration_tests/sqlmesh/macros/macros.py +8 -0
- package/recce-source/integration_tests/sqlmesh/macros/macros.sql +8 -0
- package/recce-source/integration_tests/sqlmesh/macros/utils.py +11 -0
- package/recce-source/integration_tests/sqlmesh/metrics/metrics.sql +25 -0
- package/recce-source/integration_tests/sqlmesh/models/customer_revenue_by_day.sql +41 -0
- package/recce-source/integration_tests/sqlmesh/models/customer_revenue_lifetime.sql +60 -0
- package/recce-source/integration_tests/sqlmesh/models/customers.sql +32 -0
- package/recce-source/integration_tests/sqlmesh/models/items.py +95 -0
- package/recce-source/integration_tests/sqlmesh/models/marketing.sql +15 -0
- package/recce-source/integration_tests/sqlmesh/models/order_items.py +95 -0
- package/recce-source/integration_tests/sqlmesh/models/orders.py +70 -0
- package/recce-source/integration_tests/sqlmesh/models/raw_marketing.py +62 -0
- package/recce-source/integration_tests/sqlmesh/models/top_waiters.sql +23 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_as_customer_by_day.sql +29 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_names.sql +10 -0
- package/recce-source/integration_tests/sqlmesh/models/waiter_revenue_by_day.sql +29 -0
- package/recce-source/integration_tests/sqlmesh/models/waiters.py +62 -0
- package/recce-source/integration_tests/sqlmesh/prep_env.sh +16 -0
- package/recce-source/integration_tests/sqlmesh/schema.yaml +5 -0
- package/recce-source/integration_tests/sqlmesh/seeds/waiter_names.csv +11 -0
- package/recce-source/integration_tests/sqlmesh/test_server.sh +29 -0
- package/recce-source/integration_tests/sqlmesh/tests/test_customer_revenue_by_day.yaml +63 -0
- package/recce-source/integration_tests/sqlmesh/tests/test_order_items.yaml +72 -0
- package/recce-source/js/.editorconfig +27 -0
- package/recce-source/js/.env.development +5 -0
- package/recce-source/js/.husky/pre-commit +29 -0
- package/recce-source/js/.nvmrc +1 -0
- package/recce-source/js/README.md +39 -0
- package/recce-source/js/app/(mainComponents)/DisplayModeToggle.tsx +65 -0
- package/recce-source/js/app/(mainComponents)/NavBar.tsx +228 -0
- package/recce-source/js/app/(mainComponents)/RecceVersionBadge.tsx +107 -0
- package/recce-source/js/app/(mainComponents)/TopBar.tsx +252 -0
- package/recce-source/js/app/@lineage/default.tsx +20 -0
- package/recce-source/js/app/@lineage/page.tsx +14 -0
- package/recce-source/js/app/MainLayout.tsx +170 -0
- package/recce-source/js/app/Providers.tsx +49 -0
- package/recce-source/js/app/checks/page.tsx +296 -0
- package/recce-source/js/app/error.tsx +93 -0
- package/recce-source/js/app/favicon.ico +0 -0
- package/recce-source/js/app/global-error.tsx +115 -0
- package/recce-source/js/app/global.css +82 -0
- package/recce-source/js/app/layout.tsx +48 -0
- package/recce-source/js/app/lineage/page.tsx +15 -0
- package/recce-source/js/app/page.tsx +12 -0
- package/recce-source/js/app/query/page.tsx +8 -0
- package/recce-source/js/biome.json +313 -0
- package/recce-source/js/jest.config.js +34 -0
- package/recce-source/js/jest.globals.d.ts +32 -0
- package/recce-source/js/jest.setup.js +91 -0
- package/recce-source/js/next.config.js +16 -0
- package/recce-source/js/package-lock.json +13843 -0
- package/recce-source/js/package.json +123 -0
- package/recce-source/js/pnpm-lock.yaml +9235 -0
- package/recce-source/js/pnpm-workspace.yaml +6 -0
- package/recce-source/js/postcss.config.js +5 -0
- package/recce-source/js/public/auth_callback.html +68 -0
- package/recce-source/js/public/imgs/feedback/thumbs-down.png +0 -0
- package/recce-source/js/public/imgs/feedback/thumbs-up.png +0 -0
- package/recce-source/js/public/imgs/reload-image.svg +4 -0
- package/recce-source/js/public/logo/recce-logo-white.png +0 -0
- package/recce-source/js/src/components/AuthModal/AuthModal.tsx +202 -0
- package/recce-source/js/src/components/app/AvatarDropdown.tsx +159 -0
- package/recce-source/js/src/components/app/EnvInfo.tsx +357 -0
- package/recce-source/js/src/components/app/Filename.tsx +388 -0
- package/recce-source/js/src/components/app/SetupConnectionPopover.tsx +91 -0
- package/recce-source/js/src/components/app/StateExporter.tsx +57 -0
- package/recce-source/js/src/components/app/StateImporter.tsx +198 -0
- package/recce-source/js/src/components/app/StateSharing.tsx +145 -0
- package/recce-source/js/src/components/app/StateSynchronizer.tsx +205 -0
- package/recce-source/js/src/components/charts/HistogramChart.tsx +291 -0
- package/recce-source/js/src/components/charts/SquareIcon.tsx +51 -0
- package/recce-source/js/src/components/charts/TopKSummaryList.tsx +457 -0
- package/recce-source/js/src/components/charts/chartTheme.ts +74 -0
- package/recce-source/js/src/components/check/CheckBreadcrumb.tsx +97 -0
- package/recce-source/js/src/components/check/CheckDescription.tsx +134 -0
- package/recce-source/js/src/components/check/CheckDetail.tsx +797 -0
- package/recce-source/js/src/components/check/CheckEmptyState.tsx +84 -0
- package/recce-source/js/src/components/check/CheckList.tsx +320 -0
- package/recce-source/js/src/components/check/LineageDiffView.tsx +32 -0
- package/recce-source/js/src/components/check/PresetCheckTemplateView.tsx +48 -0
- package/recce-source/js/src/components/check/SchemaDiffView.tsx +290 -0
- package/recce-source/js/src/components/check/check.ts +25 -0
- package/recce-source/js/src/components/check/timeline/CheckTimeline.tsx +163 -0
- package/recce-source/js/src/components/check/timeline/CommentInput.tsx +84 -0
- package/recce-source/js/src/components/check/timeline/TimelineEvent.tsx +468 -0
- package/recce-source/js/src/components/check/timeline/index.ts +12 -0
- package/recce-source/js/src/components/check/utils.ts +12 -0
- package/recce-source/js/src/components/data-grid/ScreenshotDataGrid.tsx +333 -0
- package/recce-source/js/src/components/data-grid/agGridStyles.css +55 -0
- package/recce-source/js/src/components/data-grid/agGridTheme.ts +43 -0
- package/recce-source/js/src/components/editor/CodeEditor.tsx +107 -0
- package/recce-source/js/src/components/editor/DiffEditor.tsx +162 -0
- package/recce-source/js/src/components/editor/index.ts +12 -0
- package/recce-source/js/src/components/errorboundary/ErrorBoundary.tsx +87 -0
- package/recce-source/js/src/components/histogram/HistogramDiffForm.tsx +147 -0
- package/recce-source/js/src/components/histogram/HistogramDiffResultView.tsx +63 -0
- package/recce-source/js/src/components/icons/index.tsx +142 -0
- package/recce-source/js/src/components/lineage/ActionControl.tsx +63 -0
- package/recce-source/js/src/components/lineage/ActionTag.tsx +141 -0
- package/recce-source/js/src/components/lineage/ChangeStatusLegend.tsx +46 -0
- package/recce-source/js/src/components/lineage/ColumnLevelLineageControl.tsx +327 -0
- package/recce-source/js/src/components/lineage/ColumnLevelLineageLegend.tsx +57 -0
- package/recce-source/js/src/components/lineage/GraphColumnNode.tsx +199 -0
- package/recce-source/js/src/components/lineage/GraphEdge.tsx +59 -0
- package/recce-source/js/src/components/lineage/GraphNode.tsx +555 -0
- package/recce-source/js/src/components/lineage/LineagePage.tsx +10 -0
- package/recce-source/js/src/components/lineage/LineageView.tsx +1384 -0
- package/recce-source/js/src/components/lineage/LineageViewContext.tsx +86 -0
- package/recce-source/js/src/components/lineage/LineageViewContextMenu.tsx +637 -0
- package/recce-source/js/src/components/lineage/LineageViewNotification.tsx +64 -0
- package/recce-source/js/src/components/lineage/LineageViewTopBar.tsx +596 -0
- package/recce-source/js/src/components/lineage/NodeSqlView.tsx +136 -0
- package/recce-source/js/src/components/lineage/NodeTag.tsx +278 -0
- package/recce-source/js/src/components/lineage/NodeView.tsx +642 -0
- package/recce-source/js/src/components/lineage/SandboxView.tsx +436 -0
- package/recce-source/js/src/components/lineage/ServerDisconnectedModalContent.tsx +105 -0
- package/recce-source/js/src/components/lineage/SetupConnectionBanner.tsx +52 -0
- package/recce-source/js/src/components/lineage/SingleEnvironmentQueryView.tsx +152 -0
- package/recce-source/js/src/components/lineage/graph.test.ts +31 -0
- package/recce-source/js/src/components/lineage/graph.ts +58 -0
- package/recce-source/js/src/components/lineage/lineage.test.ts +169 -0
- package/recce-source/js/src/components/lineage/lineage.ts +521 -0
- package/recce-source/js/src/components/lineage/styles.css +42 -0
- package/recce-source/js/src/components/lineage/styles.tsx +165 -0
- package/recce-source/js/src/components/lineage/useMultiNodesAction.ts +352 -0
- package/recce-source/js/src/components/lineage/useValueDiffAlertDialog.tsx +108 -0
- package/recce-source/js/src/components/onboarding-guide/Notification.tsx +62 -0
- package/recce-source/js/src/components/profile/ProfileDiffForm.tsx +134 -0
- package/recce-source/js/src/components/profile/ProfileDiffResultView.tsx +245 -0
- package/recce-source/js/src/components/query/ChangedOnlyCheckbox.tsx +29 -0
- package/recce-source/js/src/components/query/DiffText.tsx +120 -0
- package/recce-source/js/src/components/query/QueryDiffResultView.tsx +470 -0
- package/recce-source/js/src/components/query/QueryForm.tsx +80 -0
- package/recce-source/js/src/components/query/QueryPage.tsx +282 -0
- package/recce-source/js/src/components/query/QueryResultView.tsx +180 -0
- package/recce-source/js/src/components/query/SetupConnectionGuide.tsx +57 -0
- package/recce-source/js/src/components/query/SqlEditor.tsx +245 -0
- package/recce-source/js/src/components/query/ToggleSwitch.tsx +84 -0
- package/recce-source/js/src/components/query/styles.css +21 -0
- package/recce-source/js/src/components/routing/DirectUrlAccess.test.tsx +428 -0
- package/recce-source/js/src/components/routing/LineageStatePreservation.test.tsx +311 -0
- package/recce-source/js/src/components/routing/Navigation.test.tsx +256 -0
- package/recce-source/js/src/components/rowcount/RowCountDiffResultView.tsx +109 -0
- package/recce-source/js/src/components/rowcount/delta.ts +11 -0
- package/recce-source/js/src/components/run/RunList.tsx +303 -0
- package/recce-source/js/src/components/run/RunModal.tsx +191 -0
- package/recce-source/js/src/components/run/RunPage.tsx +26 -0
- package/recce-source/js/src/components/run/RunResultPane.tsx +454 -0
- package/recce-source/js/src/components/run/RunStatusAndDate.tsx +106 -0
- package/recce-source/js/src/components/run/RunToolbar.tsx +70 -0
- package/recce-source/js/src/components/run/RunView.tsx +196 -0
- package/recce-source/js/src/components/run/registry.ts +214 -0
- package/recce-source/js/src/components/run/types.ts +14 -0
- package/recce-source/js/src/components/schema/ColumnNameCell.test.tsx +169 -0
- package/recce-source/js/src/components/schema/ColumnNameCell.tsx +198 -0
- package/recce-source/js/src/components/schema/SchemaView.tsx +337 -0
- package/recce-source/js/src/components/schema/schemaDiff.ts +32 -0
- package/recce-source/js/src/components/schema/style.css +134 -0
- package/recce-source/js/src/components/screenshot/ScreenshotBox.tsx +39 -0
- package/recce-source/js/src/components/shared/HistoryToggle.tsx +35 -0
- package/recce-source/js/src/components/split/Split.tsx +40 -0
- package/recce-source/js/src/components/split/styles.css +24 -0
- package/recce-source/js/src/components/summary/ChangeSummary.tsx +264 -0
- package/recce-source/js/src/components/summary/SchemaSummary.tsx +123 -0
- package/recce-source/js/src/components/summary/SummaryView.tsx +29 -0
- package/recce-source/js/src/components/timeout/IdleTimeoutBadge.tsx +48 -0
- package/recce-source/js/src/components/top-k/TopKDiffForm.tsx +58 -0
- package/recce-source/js/src/components/top-k/TopKDiffResultView.tsx +73 -0
- package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnGroupHeader.tsx +228 -0
- package/recce-source/js/src/components/ui/dataGrid/DataFrameColumnHeader.tsx +113 -0
- package/recce-source/js/src/components/ui/dataGrid/defaultRenderCell.tsx +72 -0
- package/recce-source/js/src/components/ui/dataGrid/index.ts +23 -0
- package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.test.tsx +607 -0
- package/recce-source/js/src/components/ui/dataGrid/inlineRenderCell.tsx +211 -0
- package/recce-source/js/src/components/ui/dataGrid/schemaCells.test.tsx +452 -0
- package/recce-source/js/src/components/ui/dataGrid/schemaCells.tsx +142 -0
- package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.test.tsx +178 -0
- package/recce-source/js/src/components/ui/dataGrid/valueDiffCells.tsx +275 -0
- package/recce-source/js/src/components/ui/markdown/ExternalLinkConfirmDialog.tsx +134 -0
- package/recce-source/js/src/components/ui/markdown/MarkdownContent.tsx +364 -0
- package/recce-source/js/src/components/ui/mui/index.ts +13 -0
- package/recce-source/js/src/components/ui/mui-provider.tsx +67 -0
- package/recce-source/js/src/components/ui/mui-theme.ts +1039 -0
- package/recce-source/js/src/components/ui/mui-utils.ts +113 -0
- package/recce-source/js/src/components/ui/toaster.tsx +288 -0
- package/recce-source/js/src/components/valuediff/ValueDiffDetailResultView.tsx +217 -0
- package/recce-source/js/src/components/valuediff/ValueDiffForm.tsx +246 -0
- package/recce-source/js/src/components/valuediff/ValueDiffResultView.tsx +82 -0
- package/recce-source/js/src/components/valuediff/shared.ts +33 -0
- package/recce-source/js/src/constants/tooltipMessage.ts +3 -0
- package/recce-source/js/src/constants/urls.ts +1 -0
- package/recce-source/js/src/lib/UrlHash.ts +12 -0
- package/recce-source/js/src/lib/api/adhocQuery.ts +70 -0
- package/recce-source/js/src/lib/api/axiosClient.ts +9 -0
- package/recce-source/js/src/lib/api/cacheKeys.ts +13 -0
- package/recce-source/js/src/lib/api/checkEvents.ts +252 -0
- package/recce-source/js/src/lib/api/checks.ts +129 -0
- package/recce-source/js/src/lib/api/cll.ts +53 -0
- package/recce-source/js/src/lib/api/connectToCloud.ts +13 -0
- package/recce-source/js/src/lib/api/flag.ts +37 -0
- package/recce-source/js/src/lib/api/info.ts +198 -0
- package/recce-source/js/src/lib/api/instanceInfo.ts +25 -0
- package/recce-source/js/src/lib/api/keepAlive.ts +108 -0
- package/recce-source/js/src/lib/api/lineagecheck.ts +35 -0
- package/recce-source/js/src/lib/api/localStorageKeys.ts +7 -0
- package/recce-source/js/src/lib/api/models.ts +59 -0
- package/recce-source/js/src/lib/api/profile.ts +65 -0
- package/recce-source/js/src/lib/api/rowcount.ts +19 -0
- package/recce-source/js/src/lib/api/runs.ts +174 -0
- package/recce-source/js/src/lib/api/schemacheck.ts +31 -0
- package/recce-source/js/src/lib/api/select.ts +25 -0
- package/recce-source/js/src/lib/api/sessionStorageKeys.ts +8 -0
- package/recce-source/js/src/lib/api/state.ts +117 -0
- package/recce-source/js/src/lib/api/track.ts +281 -0
- package/recce-source/js/src/lib/api/types.ts +284 -0
- package/recce-source/js/src/lib/api/user.ts +42 -0
- package/recce-source/js/src/lib/api/valuediff.ts +46 -0
- package/recce-source/js/src/lib/api/version.ts +40 -0
- package/recce-source/js/src/lib/const.ts +9 -0
- package/recce-source/js/src/lib/dataGrid/crossFunctionConsistency.test.ts +626 -0
- package/recce-source/js/src/lib/dataGrid/dataGridFactory.test.ts +2140 -0
- package/recce-source/js/src/lib/dataGrid/dataGridFactory.ts +397 -0
- package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.test.ts +132 -0
- package/recce-source/js/src/lib/dataGrid/generators/rowCountUtils.ts +126 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.test.ts +1627 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataDiffGrid.ts +140 -0
- package/recce-source/js/src/lib/dataGrid/generators/toDataGrid.ts +67 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.test.ts +142 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDataGrid.ts +71 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.test.ts +258 -0
- package/recce-source/js/src/lib/dataGrid/generators/toRowCountDiffDataGrid.ts +153 -0
- package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.test.ts +951 -0
- package/recce-source/js/src/lib/dataGrid/generators/toSchemaDataGrid.ts +221 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.test.ts +395 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDataGrid.ts +184 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.test.ts +884 -0
- package/recce-source/js/src/lib/dataGrid/generators/toValueDiffGrid.ts +113 -0
- package/recce-source/js/src/lib/dataGrid/index.ts +51 -0
- package/recce-source/js/src/lib/dataGrid/propertyBased.test.ts +858 -0
- package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.test.ts +482 -0
- package/recce-source/js/src/lib/dataGrid/shared/columnBuilders.ts +345 -0
- package/recce-source/js/src/lib/dataGrid/shared/dataTypeEdgeCases.test.ts +698 -0
- package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.test.tsx +820 -0
- package/recce-source/js/src/lib/dataGrid/shared/diffColumnBuilder.tsx +277 -0
- package/recce-source/js/src/lib/dataGrid/shared/gridUtils.test.ts +785 -0
- package/recce-source/js/src/lib/dataGrid/shared/gridUtils.ts +370 -0
- package/recce-source/js/src/lib/dataGrid/shared/index.ts +81 -0
- package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.test.ts +909 -0
- package/recce-source/js/src/lib/dataGrid/shared/rowBuilders.ts +325 -0
- package/recce-source/js/src/lib/dataGrid/shared/simpleColumnBuilder.tsx +240 -0
- package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.test.tsx +719 -0
- package/recce-source/js/src/lib/dataGrid/shared/toDiffColumn.tsx +231 -0
- package/recce-source/js/src/lib/dataGrid/shared/validation.test.ts +559 -0
- package/recce-source/js/src/lib/dataGrid/shared/validation.ts +367 -0
- package/recce-source/js/src/lib/dataGrid/warehouseNamingConventions.test.ts +1117 -0
- package/recce-source/js/src/lib/formatSelect.ts +50 -0
- package/recce-source/js/src/lib/hooks/ApiConfigContext.tsx +181 -0
- package/recce-source/js/src/lib/hooks/IdleTimeoutContext.tsx +177 -0
- package/recce-source/js/src/lib/hooks/LineageGraphContext.tsx +512 -0
- package/recce-source/js/src/lib/hooks/RecceActionContext.tsx +269 -0
- package/recce-source/js/src/lib/hooks/RecceCheckContext.tsx +33 -0
- package/recce-source/js/src/lib/hooks/RecceContextProvider.tsx +54 -0
- package/recce-source/js/src/lib/hooks/RecceInstanceContext.tsx +129 -0
- package/recce-source/js/src/lib/hooks/RecceQueryContext.tsx +98 -0
- package/recce-source/js/src/lib/hooks/RecceShareStateContext.tsx +59 -0
- package/recce-source/js/src/lib/hooks/ScreenShot.tsx +399 -0
- package/recce-source/js/src/lib/hooks/useAppRouter.test.ts +211 -0
- package/recce-source/js/src/lib/hooks/useAppRouter.ts +200 -0
- package/recce-source/js/src/lib/hooks/useCheckEvents.ts +99 -0
- package/recce-source/js/src/lib/hooks/useCheckToast.tsx +14 -0
- package/recce-source/js/src/lib/hooks/useClipBoardToast.tsx +27 -0
- package/recce-source/js/src/lib/hooks/useCountdownToast.tsx +102 -0
- package/recce-source/js/src/lib/hooks/useFeedbackCollectionToast.tsx +130 -0
- package/recce-source/js/src/lib/hooks/useGuideToast.tsx +45 -0
- package/recce-source/js/src/lib/hooks/useIdleDetection.tsx +185 -0
- package/recce-source/js/src/lib/hooks/useModelColumns.tsx +113 -0
- package/recce-source/js/src/lib/hooks/useRecceInstanceInfo.tsx +13 -0
- package/recce-source/js/src/lib/hooks/useRecceServerFlag.tsx +13 -0
- package/recce-source/js/src/lib/hooks/useRun.tsx +89 -0
- package/recce-source/js/src/lib/hooks/useThemeColors.ts +115 -0
- package/recce-source/js/src/lib/mergeKeys.test.ts +89 -0
- package/recce-source/js/src/lib/mergeKeys.ts +86 -0
- package/recce-source/js/src/lib/result/ResultErrorFallback.tsx +9 -0
- package/recce-source/js/src/lib/utils/formatTime.ts +84 -0
- package/recce-source/js/src/lib/utils/urls.ts +16 -0
- package/recce-source/js/src/utils/DropdownValuesInput.tsx +297 -0
- package/recce-source/js/src/utils/formatters.tsx +237 -0
- package/recce-source/js/src/utils/transforms.ts +81 -0
- package/recce-source/js/tsconfig.json +47 -0
- package/recce-source/macros/README.md +8 -0
- package/recce-source/macros/recce_athena.sql +73 -0
- package/recce-source/pyproject.toml +109 -0
- package/recce-source/recce/VERSION +1 -0
- package/recce-source/recce/__init__.py +84 -0
- package/recce-source/recce/adapter/__init__.py +0 -0
- package/recce-source/recce/adapter/base.py +109 -0
- package/recce-source/recce/adapter/dbt_adapter/__init__.py +1699 -0
- package/recce-source/recce/adapter/dbt_adapter/dbt_version.py +42 -0
- package/recce-source/recce/adapter/sqlmesh_adapter.py +141 -0
- package/recce-source/recce/apis/__init__.py +0 -0
- package/recce-source/recce/apis/check_api.py +203 -0
- package/recce-source/recce/apis/check_events_api.py +353 -0
- package/recce-source/recce/apis/check_func.py +130 -0
- package/recce-source/recce/apis/run_api.py +130 -0
- package/recce-source/recce/apis/run_func.py +258 -0
- package/recce-source/recce/artifact.py +266 -0
- package/recce-source/recce/cli.py +1846 -0
- package/recce-source/recce/config.py +127 -0
- package/recce-source/recce/connect_to_cloud.py +138 -0
- package/recce-source/recce/core.py +334 -0
- package/recce-source/recce/diff.py +26 -0
- package/recce-source/recce/event/CONFIG +1 -0
- package/recce-source/recce/event/SENTRY_DNS +1 -0
- package/recce-source/recce/event/__init__.py +304 -0
- package/recce-source/recce/event/collector.py +184 -0
- package/recce-source/recce/event/track.py +158 -0
- package/recce-source/recce/exceptions.py +21 -0
- package/recce-source/recce/git.py +77 -0
- package/recce-source/recce/github.py +222 -0
- package/recce-source/recce/mcp_server.py +861 -0
- package/recce-source/recce/models/__init__.py +6 -0
- package/recce-source/recce/models/check.py +473 -0
- package/recce-source/recce/models/run.py +46 -0
- package/recce-source/recce/models/types.py +218 -0
- package/recce-source/recce/pull_request.py +124 -0
- package/recce-source/recce/run.py +390 -0
- package/recce-source/recce/server.py +877 -0
- package/recce-source/recce/state/__init__.py +31 -0
- package/recce-source/recce/state/cloud.py +644 -0
- package/recce-source/recce/state/const.py +26 -0
- package/recce-source/recce/state/local.py +56 -0
- package/recce-source/recce/state/state.py +119 -0
- package/recce-source/recce/state/state_loader.py +174 -0
- package/recce-source/recce/summary.py +575 -0
- package/recce-source/recce/tasks/__init__.py +23 -0
- package/recce-source/recce/tasks/core.py +134 -0
- package/recce-source/recce/tasks/dataframe.py +170 -0
- package/recce-source/recce/tasks/histogram.py +433 -0
- package/recce-source/recce/tasks/lineage.py +19 -0
- package/recce-source/recce/tasks/profile.py +298 -0
- package/recce-source/recce/tasks/query.py +450 -0
- package/recce-source/recce/tasks/rowcount.py +277 -0
- package/recce-source/recce/tasks/schema.py +65 -0
- package/recce-source/recce/tasks/top_k.py +172 -0
- package/recce-source/recce/tasks/utils.py +147 -0
- package/recce-source/recce/tasks/valuediff.py +497 -0
- package/recce-source/recce/util/__init__.py +4 -0
- package/recce-source/recce/util/api_token.py +80 -0
- package/recce-source/recce/util/breaking.py +330 -0
- package/recce-source/recce/util/cache.py +25 -0
- package/recce-source/recce/util/cll.py +355 -0
- package/recce-source/recce/util/cloud/__init__.py +15 -0
- package/recce-source/recce/util/cloud/base.py +115 -0
- package/recce-source/recce/util/cloud/check_events.py +190 -0
- package/recce-source/recce/util/cloud/checks.py +242 -0
- package/recce-source/recce/util/io.py +120 -0
- package/recce-source/recce/util/lineage.py +83 -0
- package/recce-source/recce/util/logger.py +25 -0
- package/recce-source/recce/util/onboarding_state.py +45 -0
- package/recce-source/recce/util/perf_tracking.py +85 -0
- package/recce-source/recce/util/pydantic_model.py +22 -0
- package/recce-source/recce/util/recce_cloud.py +454 -0
- package/recce-source/recce/util/singleton.py +18 -0
- package/recce-source/recce/util/startup_perf.py +121 -0
- package/recce-source/recce/yaml/__init__.py +58 -0
- package/recce-source/recce_cloud/README.md +780 -0
- package/recce-source/recce_cloud/VERSION +1 -0
- package/recce-source/recce_cloud/__init__.py +24 -0
- package/recce-source/recce_cloud/api/__init__.py +17 -0
- package/recce-source/recce_cloud/api/base.py +132 -0
- package/recce-source/recce_cloud/api/client.py +186 -0
- package/recce-source/recce_cloud/api/exceptions.py +26 -0
- package/recce-source/recce_cloud/api/factory.py +63 -0
- package/recce-source/recce_cloud/api/github.py +106 -0
- package/recce-source/recce_cloud/api/gitlab.py +111 -0
- package/recce-source/recce_cloud/artifact.py +57 -0
- package/recce-source/recce_cloud/ci_providers/__init__.py +9 -0
- package/recce-source/recce_cloud/ci_providers/base.py +82 -0
- package/recce-source/recce_cloud/ci_providers/detector.py +147 -0
- package/recce-source/recce_cloud/ci_providers/github_actions.py +136 -0
- package/recce-source/recce_cloud/ci_providers/gitlab_ci.py +130 -0
- package/recce-source/recce_cloud/cli.py +434 -0
- package/recce-source/recce_cloud/download.py +230 -0
- package/recce-source/recce_cloud/hatch_build.py +20 -0
- package/recce-source/recce_cloud/pyproject.toml +49 -0
- package/recce-source/recce_cloud/upload.py +214 -0
- package/recce-source/test.py +0 -0
- package/recce-source/tests/__init__.py +0 -0
- package/recce-source/tests/adapter/__init__.py +0 -0
- package/recce-source/tests/adapter/dbt_adapter/__init__.py +0 -0
- package/recce-source/tests/adapter/dbt_adapter/conftest.py +17 -0
- package/recce-source/tests/adapter/dbt_adapter/dbt_test_helper.py +298 -0
- package/recce-source/tests/adapter/dbt_adapter/test_dbt_adapter.py +25 -0
- package/recce-source/tests/adapter/dbt_adapter/test_dbt_cll.py +717 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/dbt_project.yml +4 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/manifest.json +1 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/package-lock.yml +8 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/packages.yml +7 -0
- package/recce-source/tests/adapter/dbt_adapter/test_proj/profiles.yml +6 -0
- package/recce-source/tests/adapter/dbt_adapter/test_selector.py +205 -0
- package/recce-source/tests/apis/__init__.py +0 -0
- package/recce-source/tests/apis/row_count_diff.json +59 -0
- package/recce-source/tests/apis/test_check_events_api.py +615 -0
- package/recce-source/tests/apis/test_run_func.py +433 -0
- package/recce-source/tests/catalog.json +527 -0
- package/recce-source/tests/data/manifest/base/catalog.json +1 -0
- package/recce-source/tests/data/manifest/base/manifest.json +1 -0
- package/recce-source/tests/data/manifest/pr2/catalog.json +1 -0
- package/recce-source/tests/data/manifest/pr2/manifest.json +1 -0
- package/recce-source/tests/manifest.json +10655 -0
- package/recce-source/tests/models/__init__.py +0 -0
- package/recce-source/tests/models/test_check.py +731 -0
- package/recce-source/tests/models/test_run_models.py +295 -0
- package/recce-source/tests/recce_cloud/__init__.py +0 -0
- package/recce-source/tests/recce_cloud/test_ci_providers.py +351 -0
- package/recce-source/tests/recce_cloud/test_cli.py +735 -0
- package/recce-source/tests/recce_cloud/test_client.py +379 -0
- package/recce-source/tests/recce_cloud/test_platform_clients.py +483 -0
- package/recce-source/tests/recce_state.json +1 -0
- package/recce-source/tests/state/test_cloud.py +719 -0
- package/recce-source/tests/state/test_local.py +164 -0
- package/recce-source/tests/state/test_state_loader.py +211 -0
- package/recce-source/tests/tasks/__init__.py +0 -0
- package/recce-source/tests/tasks/conftest.py +4 -0
- package/recce-source/tests/tasks/test_histogram.py +129 -0
- package/recce-source/tests/tasks/test_lineage.py +55 -0
- package/recce-source/tests/tasks/test_preset_checks.py +64 -0
- package/recce-source/tests/tasks/test_profile.py +397 -0
- package/recce-source/tests/tasks/test_query.py +528 -0
- package/recce-source/tests/tasks/test_row_count.py +133 -0
- package/recce-source/tests/tasks/test_schema.py +122 -0
- package/recce-source/tests/tasks/test_top_k.py +77 -0
- package/recce-source/tests/tasks/test_utils.py +439 -0
- package/recce-source/tests/tasks/test_valuediff.py +361 -0
- package/recce-source/tests/test_cli.py +236 -0
- package/recce-source/tests/test_cli_mcp_optional.py +45 -0
- package/recce-source/tests/test_cloud_listing_cli.py +324 -0
- package/recce-source/tests/test_config.py +43 -0
- package/recce-source/tests/test_connect_to_cloud.py +82 -0
- package/recce-source/tests/test_core.py +174 -0
- package/recce-source/tests/test_dbt.py +36 -0
- package/recce-source/tests/test_mcp_server.py +505 -0
- package/recce-source/tests/test_pull_request.py +130 -0
- package/recce-source/tests/test_server.py +202 -0
- package/recce-source/tests/test_server_lifespan.py +138 -0
- package/recce-source/tests/test_summary.py +73 -0
- package/recce-source/tests/util/__init__.py +0 -0
- package/recce-source/tests/util/cloud/__init__.py +0 -0
- package/recce-source/tests/util/cloud/test_check_events.py +255 -0
- package/recce-source/tests/util/cloud/test_checks.py +204 -0
- package/recce-source/tests/util/test_api_token.py +119 -0
- package/recce-source/tests/util/test_breaking.py +1427 -0
- package/recce-source/tests/util/test_cll.py +706 -0
- package/recce-source/tests/util/test_lineage.py +122 -0
- package/recce-source/tests/util/test_onboarding_state.py +84 -0
- package/recce-source/tests/util/test_recce_cloud.py +231 -0
- package/recce-source/tox.ini +40 -0
- package/recce-source/uv.lock +3928 -0
- package/src/api/index.ts +32 -0
- package/src/components/index.ts +154 -0
- package/src/global.d.ts +14 -0
- package/src/hooks/index.ts +56 -0
- package/src/index.ts +17 -0
- package/src/lib/hooks/RouteConfigContext.ts +139 -0
- package/src/lib/hooks/useAppRouter.ts +240 -0
- package/src/mui-augmentation.d.ts +139 -0
- package/src/theme/index.ts +13 -0
- package/src/theme.ts +23 -0
- package/src/types/index.ts +23 -0
- package/dist/index-BNUP2V_N.d.ts.map +0 -1
- package/dist/index-DOPZuhD8.d.mts.map +0 -1
- package/dist/theme.d.mts.map +0 -1
- package/dist/theme.d.ts.map +0 -1
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
# Skip all tests in this module if mcp is not available
|
|
7
|
+
pytest.importorskip("mcp")
|
|
8
|
+
|
|
9
|
+
from recce.core import RecceContext # noqa: E402
|
|
10
|
+
from recce.mcp_server import RecceMCPServer, run_mcp_server # noqa: E402
|
|
11
|
+
from recce.models.types import LineageDiff # noqa: E402
|
|
12
|
+
from recce.server import RecceServerMode # noqa: E402
|
|
13
|
+
from recce.tasks.profile import ProfileDiffTask # noqa: E402
|
|
14
|
+
from recce.tasks.query import QueryDiffTask, QueryTask # noqa: E402
|
|
15
|
+
from recce.tasks.rowcount import RowCountDiffTask # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mcp_server():
|
|
20
|
+
"""Fixture to create a RecceMCPServer instance for testing"""
|
|
21
|
+
mock_context = MagicMock(spec=RecceContext)
|
|
22
|
+
return RecceMCPServer(mock_context), mock_context
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestRecceMCPServer:
|
|
26
|
+
"""Test cases for the RecceMCPServer class"""
|
|
27
|
+
|
|
28
|
+
def test_server_initialization(self, mcp_server):
|
|
29
|
+
"""Test that the MCP server initializes correctly"""
|
|
30
|
+
server, mock_context = mcp_server
|
|
31
|
+
assert server.context == mock_context
|
|
32
|
+
assert server.server is not None
|
|
33
|
+
assert server.server.name == "recce"
|
|
34
|
+
|
|
35
|
+
@pytest.mark.asyncio
|
|
36
|
+
async def test_tool_lineage_diff(self, mcp_server):
|
|
37
|
+
"""Test the lineage_diff tool"""
|
|
38
|
+
server, mock_context = mcp_server
|
|
39
|
+
# Mock the lineage diff response
|
|
40
|
+
mock_lineage_diff = MagicMock(spec=LineageDiff)
|
|
41
|
+
mock_lineage_diff.model_dump.return_value = {
|
|
42
|
+
"base": {
|
|
43
|
+
"nodes": {
|
|
44
|
+
"model.project.model_a": {
|
|
45
|
+
"name": "model_a",
|
|
46
|
+
"resource_type": "model",
|
|
47
|
+
"config": {"materialized": "view"},
|
|
48
|
+
},
|
|
49
|
+
"model.project.model_b": {
|
|
50
|
+
"name": "model_b",
|
|
51
|
+
"resource_type": "model",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
"parent_map": {
|
|
55
|
+
"model.project.model_a": [],
|
|
56
|
+
"model.project.model_b": ["model.project.model_a"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
"current": {
|
|
60
|
+
"nodes": {
|
|
61
|
+
"model.project.model_a": {
|
|
62
|
+
"name": "model_a",
|
|
63
|
+
"resource_type": "model",
|
|
64
|
+
"config": {"materialized": "view"},
|
|
65
|
+
},
|
|
66
|
+
"model.project.model_b": {
|
|
67
|
+
"name": "model_b",
|
|
68
|
+
"resource_type": "model",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
"parent_map": {
|
|
72
|
+
"model.project.model_a": [],
|
|
73
|
+
"model.project.model_b": ["model.project.model_a"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
"diff": {
|
|
77
|
+
"model.project.model_a": {"change_status": "modified"},
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
mock_context.get_lineage_diff.return_value = mock_lineage_diff
|
|
81
|
+
mock_context.adapter.select_nodes.return_value = {
|
|
82
|
+
"model.project.model_a",
|
|
83
|
+
"model.project.model_b",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Execute the method
|
|
87
|
+
result = await server._tool_lineage_diff({})
|
|
88
|
+
|
|
89
|
+
# Verify the result structure
|
|
90
|
+
assert "nodes" in result
|
|
91
|
+
assert "edges" in result
|
|
92
|
+
|
|
93
|
+
# Verify nodes is a DataFrame dict with columns and data
|
|
94
|
+
nodes = result["nodes"]
|
|
95
|
+
assert "columns" in nodes
|
|
96
|
+
assert "data" in nodes
|
|
97
|
+
|
|
98
|
+
# Verify data is a list with 2 rows
|
|
99
|
+
assert isinstance(nodes["data"], list)
|
|
100
|
+
assert len(nodes["data"]) == 2
|
|
101
|
+
|
|
102
|
+
# Verify edges is a DataFrame dict with columns and data
|
|
103
|
+
edges = result["edges"]
|
|
104
|
+
assert "columns" in edges
|
|
105
|
+
assert "data" in edges
|
|
106
|
+
assert isinstance(edges["data"], list)
|
|
107
|
+
# Verify edges contains the parent-child relationship (model_a -> model_b)
|
|
108
|
+
assert len(edges["data"]) == 1
|
|
109
|
+
|
|
110
|
+
mock_context.get_lineage_diff.assert_called_once()
|
|
111
|
+
mock_context.adapter.select_nodes.assert_called()
|
|
112
|
+
|
|
113
|
+
@pytest.mark.asyncio
|
|
114
|
+
async def test_tool_schema_diff(self, mcp_server):
|
|
115
|
+
"""Test the schema_diff tool"""
|
|
116
|
+
server, mock_context = mcp_server
|
|
117
|
+
# Mock the lineage diff response with schema information
|
|
118
|
+
mock_lineage_diff = MagicMock(spec=LineageDiff)
|
|
119
|
+
mock_lineage_diff.model_dump.return_value = {
|
|
120
|
+
"base": {
|
|
121
|
+
"nodes": {
|
|
122
|
+
"model.project.model_a": {
|
|
123
|
+
"name": "model_a",
|
|
124
|
+
"resource_type": "model",
|
|
125
|
+
"columns": {
|
|
126
|
+
"id": {"name": "id", "type": "integer"},
|
|
127
|
+
"name": {"name": "name", "type": "text"},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
"current": {
|
|
133
|
+
"nodes": {
|
|
134
|
+
"model.project.model_a": {
|
|
135
|
+
"name": "model_a",
|
|
136
|
+
"resource_type": "model",
|
|
137
|
+
"columns": {
|
|
138
|
+
"id": {"name": "id", "type": "integer"},
|
|
139
|
+
"name": {"name": "name", "type": "text"},
|
|
140
|
+
"age": {"name": "age", "type": "integer"},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
mock_context.get_lineage_diff.return_value = mock_lineage_diff
|
|
147
|
+
mock_context.adapter.select_nodes.return_value = {"model.project.model_a"}
|
|
148
|
+
|
|
149
|
+
# Execute the method
|
|
150
|
+
result = await server._tool_schema_diff({})
|
|
151
|
+
|
|
152
|
+
# Verify the result is a DataFrame dict with columns and data
|
|
153
|
+
assert "columns" in result
|
|
154
|
+
assert "data" in result
|
|
155
|
+
assert "limit" in result
|
|
156
|
+
assert "more" in result
|
|
157
|
+
|
|
158
|
+
# Verify limit and more fields
|
|
159
|
+
assert result["limit"] == 100
|
|
160
|
+
assert isinstance(result["more"], bool)
|
|
161
|
+
assert isinstance(result["data"], list)
|
|
162
|
+
# Verify the data contains the added column
|
|
163
|
+
assert len(result["data"]) > 0
|
|
164
|
+
|
|
165
|
+
mock_context.get_lineage_diff.assert_called_once()
|
|
166
|
+
|
|
167
|
+
@pytest.mark.asyncio
|
|
168
|
+
async def test_tool_row_count_diff(self, mcp_server):
|
|
169
|
+
"""Test the row_count_diff tool"""
|
|
170
|
+
server, _ = mcp_server
|
|
171
|
+
# Mock the task execution
|
|
172
|
+
mock_result = {"results": [{"node_id": "model.project.my_model", "base": 100, "current": 105, "diff": 5}]}
|
|
173
|
+
|
|
174
|
+
with patch.object(RowCountDiffTask, "execute", return_value=mock_result):
|
|
175
|
+
result = await server._tool_row_count_diff({"node_names": ["my_model"]})
|
|
176
|
+
|
|
177
|
+
# Verify the result
|
|
178
|
+
assert result == mock_result
|
|
179
|
+
assert "results" in result
|
|
180
|
+
|
|
181
|
+
@pytest.mark.asyncio
|
|
182
|
+
async def test_tool_query(self, mcp_server):
|
|
183
|
+
"""Test the query tool"""
|
|
184
|
+
server, _ = mcp_server
|
|
185
|
+
# Mock the task execution
|
|
186
|
+
mock_result = MagicMock()
|
|
187
|
+
mock_result.model_dump.return_value = {
|
|
188
|
+
"columns": ["id", "name"],
|
|
189
|
+
"data": [[1, "Alice"], [2, "Bob"]],
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
with patch.object(QueryTask, "execute", return_value=mock_result):
|
|
193
|
+
result = await server._tool_query({"sql_template": "SELECT * FROM {{ ref('my_model') }}", "base": False})
|
|
194
|
+
|
|
195
|
+
# Verify the result
|
|
196
|
+
assert "columns" in result
|
|
197
|
+
assert "data" in result
|
|
198
|
+
mock_result.model_dump.assert_called_once_with(mode="json")
|
|
199
|
+
|
|
200
|
+
@pytest.mark.asyncio
|
|
201
|
+
async def test_tool_query_with_base_flag(self, mcp_server):
|
|
202
|
+
"""Test the query tool with base environment flag"""
|
|
203
|
+
server, _ = mcp_server
|
|
204
|
+
mock_result = {"columns": ["id"], "data": [[1]]}
|
|
205
|
+
|
|
206
|
+
with patch.object(QueryTask, "execute", return_value=mock_result) as mock_execute:
|
|
207
|
+
with patch.object(QueryTask, "__init__", return_value=None):
|
|
208
|
+
task = QueryTask(params={"sql_template": "SELECT 1"})
|
|
209
|
+
task.is_base = True
|
|
210
|
+
task.execute = mock_execute
|
|
211
|
+
|
|
212
|
+
result = await server._tool_query({"sql_template": "SELECT 1", "base": True})
|
|
213
|
+
|
|
214
|
+
# Verify base flag was set (would need to inspect task creation)
|
|
215
|
+
assert result == mock_result
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
async def test_tool_query_diff(self, mcp_server):
|
|
219
|
+
"""Test the query_diff tool"""
|
|
220
|
+
server, _ = mcp_server
|
|
221
|
+
# Mock the task execution
|
|
222
|
+
mock_result = MagicMock()
|
|
223
|
+
mock_result.model_dump.return_value = {
|
|
224
|
+
"diff": {
|
|
225
|
+
"added": [[3, "Charlie"]],
|
|
226
|
+
"removed": [[1, "Alice"]],
|
|
227
|
+
"modified": [],
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
with patch.object(QueryDiffTask, "execute", return_value=mock_result):
|
|
232
|
+
result = await server._tool_query_diff(
|
|
233
|
+
{
|
|
234
|
+
"sql_template": "SELECT * FROM {{ ref('my_model') }}",
|
|
235
|
+
"primary_keys": ["id"],
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Verify the result
|
|
240
|
+
assert "diff" in result
|
|
241
|
+
mock_result.model_dump.assert_called_once_with(mode="json")
|
|
242
|
+
|
|
243
|
+
@pytest.mark.asyncio
|
|
244
|
+
async def test_tool_profile_diff(self, mcp_server):
|
|
245
|
+
"""Test the profile_diff tool"""
|
|
246
|
+
server, _ = mcp_server
|
|
247
|
+
# Mock the task execution
|
|
248
|
+
mock_result = MagicMock()
|
|
249
|
+
mock_result.model_dump.return_value = {
|
|
250
|
+
"columns": {
|
|
251
|
+
"id": {
|
|
252
|
+
"base": {"min": 1, "max": 100, "avg": 50.5},
|
|
253
|
+
"current": {"min": 1, "max": 105, "avg": 53.0},
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
with patch.object(ProfileDiffTask, "execute", return_value=mock_result):
|
|
259
|
+
result = await server._tool_profile_diff({"model": "my_model", "columns": ["id"]})
|
|
260
|
+
|
|
261
|
+
# Verify the result
|
|
262
|
+
assert "columns" in result
|
|
263
|
+
mock_result.model_dump.assert_called_once_with(mode="json")
|
|
264
|
+
|
|
265
|
+
@pytest.mark.asyncio
|
|
266
|
+
async def test_tool_list_checks(self, mcp_server):
|
|
267
|
+
"""Test the list_checks tool"""
|
|
268
|
+
server, _ = mcp_server
|
|
269
|
+
from uuid import uuid4
|
|
270
|
+
|
|
271
|
+
from recce.models.types import RunType
|
|
272
|
+
|
|
273
|
+
check_id = uuid4()
|
|
274
|
+
mock_check = MagicMock()
|
|
275
|
+
mock_check.check_id = check_id
|
|
276
|
+
mock_check.name = "Test Check"
|
|
277
|
+
mock_check.type = RunType.SCHEMA_DIFF
|
|
278
|
+
mock_check.description = "Test description"
|
|
279
|
+
mock_check.params = {"select": "model_a"}
|
|
280
|
+
mock_check.is_checked = True
|
|
281
|
+
mock_check.is_preset = False
|
|
282
|
+
|
|
283
|
+
mock_check_dao = MagicMock()
|
|
284
|
+
mock_check_dao.list.return_value = [mock_check]
|
|
285
|
+
mock_check_dao.status.return_value = {"total": 1, "approved": 1}
|
|
286
|
+
|
|
287
|
+
with patch("recce.models.CheckDAO", return_value=mock_check_dao):
|
|
288
|
+
result = await server._tool_list_checks({})
|
|
289
|
+
|
|
290
|
+
# Verify the result structure
|
|
291
|
+
assert "checks" in result
|
|
292
|
+
assert "total" in result
|
|
293
|
+
assert "approved" in result
|
|
294
|
+
assert len(result["checks"]) == 1
|
|
295
|
+
assert result["checks"][0]["check_id"] == str(check_id)
|
|
296
|
+
assert result["checks"][0]["name"] == "Test Check"
|
|
297
|
+
assert result["checks"][0]["type"] == "schema_diff"
|
|
298
|
+
assert result["total"] == 1
|
|
299
|
+
assert result["approved"] == 1
|
|
300
|
+
|
|
301
|
+
@pytest.mark.asyncio
|
|
302
|
+
async def test_tool_run_check_row_count_diff(self, mcp_server):
|
|
303
|
+
"""Test running a row_count_diff check"""
|
|
304
|
+
server, _ = mcp_server
|
|
305
|
+
from uuid import uuid4
|
|
306
|
+
|
|
307
|
+
from recce.models.types import RunType
|
|
308
|
+
|
|
309
|
+
check_id = uuid4()
|
|
310
|
+
run_id = uuid4()
|
|
311
|
+
|
|
312
|
+
# Create mock check with row_count_diff type
|
|
313
|
+
mock_check = MagicMock()
|
|
314
|
+
mock_check.check_id = check_id
|
|
315
|
+
mock_check.name = "Row Count Check"
|
|
316
|
+
mock_check.type = RunType.ROW_COUNT_DIFF
|
|
317
|
+
mock_check.params = {"node_names": ["model_a"]}
|
|
318
|
+
|
|
319
|
+
# Create mock run
|
|
320
|
+
mock_run = MagicMock()
|
|
321
|
+
mock_run.run_id = run_id
|
|
322
|
+
mock_run.type = RunType.ROW_COUNT_DIFF
|
|
323
|
+
mock_run.result = {"results": [{"node_id": "model.project.model_a", "base": 100, "current": 105, "diff": 5}]}
|
|
324
|
+
mock_run.error = None
|
|
325
|
+
mock_run.check_id = check_id
|
|
326
|
+
mock_run.model_dump.return_value = {
|
|
327
|
+
"run_id": str(run_id),
|
|
328
|
+
"check_id": str(check_id),
|
|
329
|
+
"type": "row_count_diff",
|
|
330
|
+
"result": mock_run.result,
|
|
331
|
+
"error": None,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Mock CheckDAO and submit_run
|
|
335
|
+
mock_check_dao = MagicMock()
|
|
336
|
+
mock_check_dao.find_check_by_id.return_value = mock_check
|
|
337
|
+
|
|
338
|
+
with patch("recce.models.CheckDAO", return_value=mock_check_dao):
|
|
339
|
+
with patch("recce.apis.run_func.submit_run") as mock_submit_run:
|
|
340
|
+
mock_submit_run.return_value = (mock_run, asyncio.sleep(0))
|
|
341
|
+
result = await server._tool_run_check({"check_id": str(check_id)})
|
|
342
|
+
|
|
343
|
+
# Verify the result
|
|
344
|
+
assert "run_id" in result
|
|
345
|
+
assert result["run_id"] == str(run_id)
|
|
346
|
+
assert "type" in result
|
|
347
|
+
assert result["type"] == "row_count_diff"
|
|
348
|
+
assert "check_id" in result
|
|
349
|
+
assert result["check_id"] == str(check_id)
|
|
350
|
+
|
|
351
|
+
@pytest.mark.asyncio
|
|
352
|
+
async def test_tool_run_check_with_lineage_diff(self, mcp_server):
|
|
353
|
+
"""Test running a lineage_diff check delegates to _tool_lineage_diff"""
|
|
354
|
+
server, mock_context = mcp_server
|
|
355
|
+
from uuid import uuid4
|
|
356
|
+
|
|
357
|
+
from recce.models.types import LineageDiff, RunType
|
|
358
|
+
|
|
359
|
+
check_id = uuid4()
|
|
360
|
+
|
|
361
|
+
# Create mock check with lineage_diff type
|
|
362
|
+
mock_check = MagicMock()
|
|
363
|
+
mock_check.check_id = check_id
|
|
364
|
+
mock_check.name = "Lineage Check"
|
|
365
|
+
mock_check.type = RunType.LINEAGE_DIFF
|
|
366
|
+
mock_check.params = {"select": "model_a"}
|
|
367
|
+
|
|
368
|
+
# Mock lineage diff response
|
|
369
|
+
mock_lineage_diff = MagicMock(spec=LineageDiff)
|
|
370
|
+
mock_lineage_diff.model_dump.return_value = {
|
|
371
|
+
"base": {"nodes": {}, "parent_map": {}},
|
|
372
|
+
"current": {"nodes": {}, "parent_map": {}},
|
|
373
|
+
"diff": {},
|
|
374
|
+
}
|
|
375
|
+
mock_context.get_lineage_diff.return_value = mock_lineage_diff
|
|
376
|
+
mock_context.adapter.select_nodes.return_value = set()
|
|
377
|
+
|
|
378
|
+
# Mock CheckDAO
|
|
379
|
+
mock_check_dao = MagicMock()
|
|
380
|
+
mock_check_dao.find_check_by_id.return_value = mock_check
|
|
381
|
+
|
|
382
|
+
with patch("recce.models.CheckDAO", return_value=mock_check_dao):
|
|
383
|
+
result = await server._tool_run_check({"check_id": str(check_id)})
|
|
384
|
+
|
|
385
|
+
# Verify the result is from lineage_diff tool (has nodes and edges)
|
|
386
|
+
assert "nodes" in result
|
|
387
|
+
assert "edges" in result
|
|
388
|
+
|
|
389
|
+
@pytest.mark.asyncio
|
|
390
|
+
async def test_tool_run_check_with_schema_diff(self, mcp_server):
|
|
391
|
+
"""Test running a schema_diff check delegates to _tool_schema_diff"""
|
|
392
|
+
server, mock_context = mcp_server
|
|
393
|
+
from uuid import uuid4
|
|
394
|
+
|
|
395
|
+
from recce.models.types import LineageDiff, RunType
|
|
396
|
+
|
|
397
|
+
check_id = uuid4()
|
|
398
|
+
|
|
399
|
+
# Create mock check with schema_diff type
|
|
400
|
+
mock_check = MagicMock()
|
|
401
|
+
mock_check.check_id = check_id
|
|
402
|
+
mock_check.name = "Schema Check"
|
|
403
|
+
mock_check.type = RunType.SCHEMA_DIFF
|
|
404
|
+
mock_check.params = {"select": "model_a"}
|
|
405
|
+
|
|
406
|
+
# Mock lineage diff response
|
|
407
|
+
mock_lineage_diff = MagicMock(spec=LineageDiff)
|
|
408
|
+
mock_lineage_diff.model_dump.return_value = {
|
|
409
|
+
"base": {"nodes": {}, "parent_map": {}},
|
|
410
|
+
"current": {"nodes": {}, "parent_map": {}},
|
|
411
|
+
"diff": {},
|
|
412
|
+
}
|
|
413
|
+
mock_context.get_lineage_diff.return_value = mock_lineage_diff
|
|
414
|
+
mock_context.adapter.select_nodes.return_value = set()
|
|
415
|
+
|
|
416
|
+
# Mock CheckDAO
|
|
417
|
+
mock_check_dao = MagicMock()
|
|
418
|
+
mock_check_dao.find_check_by_id.return_value = mock_check
|
|
419
|
+
|
|
420
|
+
with patch("recce.models.CheckDAO", return_value=mock_check_dao):
|
|
421
|
+
result = await server._tool_run_check({"check_id": str(check_id)})
|
|
422
|
+
|
|
423
|
+
# Verify the result is from schema_diff tool (has columns, data, limit, more)
|
|
424
|
+
assert "columns" in result
|
|
425
|
+
assert "data" in result
|
|
426
|
+
assert "limit" in result
|
|
427
|
+
assert "more" in result
|
|
428
|
+
|
|
429
|
+
@pytest.mark.asyncio
|
|
430
|
+
async def test_error_handling(self, mcp_server):
|
|
431
|
+
"""Test error handling in tool execution"""
|
|
432
|
+
server, mock_context = mcp_server
|
|
433
|
+
# Make get_lineage_diff raise an exception
|
|
434
|
+
mock_context.get_lineage_diff.side_effect = Exception("Test error")
|
|
435
|
+
|
|
436
|
+
# The method should raise the exception
|
|
437
|
+
with pytest.raises(Exception, match="Test error"):
|
|
438
|
+
await server._tool_lineage_diff({})
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class TestRunMCPServer:
|
|
442
|
+
"""Test cases for the run_mcp_server function"""
|
|
443
|
+
|
|
444
|
+
@pytest.mark.asyncio
|
|
445
|
+
@patch("recce.mcp_server.load_context")
|
|
446
|
+
@patch.object(RecceMCPServer, "run")
|
|
447
|
+
async def test_run_mcp_server(self, mock_run, mock_load_context):
|
|
448
|
+
"""Test the run_mcp_server entry point"""
|
|
449
|
+
# Mock the context
|
|
450
|
+
mock_context = MagicMock(spec=RecceContext)
|
|
451
|
+
mock_load_context.return_value = mock_context
|
|
452
|
+
|
|
453
|
+
# Mock the server run method
|
|
454
|
+
mock_run.return_value = None
|
|
455
|
+
|
|
456
|
+
# Run the server
|
|
457
|
+
await run_mcp_server(project_dir="/test/path")
|
|
458
|
+
|
|
459
|
+
# Verify context was loaded with correct kwargs
|
|
460
|
+
mock_load_context.assert_called_once_with(project_dir="/test/path")
|
|
461
|
+
|
|
462
|
+
# Verify server was run
|
|
463
|
+
mock_run.assert_called_once()
|
|
464
|
+
|
|
465
|
+
@pytest.mark.asyncio
|
|
466
|
+
@patch("recce.mcp_server.load_context")
|
|
467
|
+
async def test_run_mcp_server_context_error(self, mock_load_context):
|
|
468
|
+
"""Test run_mcp_server handles context loading errors"""
|
|
469
|
+
# Make load_context raise an exception
|
|
470
|
+
mock_load_context.side_effect = FileNotFoundError("manifest.json not found")
|
|
471
|
+
|
|
472
|
+
# The function should raise the exception
|
|
473
|
+
with pytest.raises(FileNotFoundError):
|
|
474
|
+
await run_mcp_server()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def test_mcp_cli_command_exists():
|
|
478
|
+
"""Test that the mcp-server CLI command is registered"""
|
|
479
|
+
from recce.cli import cli
|
|
480
|
+
|
|
481
|
+
# Check that mcp_server is in the CLI commands
|
|
482
|
+
commands = [cmd.name for cmd in cli.commands.values()]
|
|
483
|
+
assert "mcp_server" in commands or "mcp-server" in commands
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class TestMCPServerModes:
|
|
487
|
+
"""Test cases for MCP server mode functionality"""
|
|
488
|
+
|
|
489
|
+
def test_server_mode_default(self):
|
|
490
|
+
"""Test that server mode is the default when not specified"""
|
|
491
|
+
mock_context = MagicMock(spec=RecceContext)
|
|
492
|
+
server = RecceMCPServer(mock_context)
|
|
493
|
+
|
|
494
|
+
# Default mode should be server
|
|
495
|
+
assert server.mode == RecceServerMode.server
|
|
496
|
+
|
|
497
|
+
def test_non_server_mode_restricts_tools(self):
|
|
498
|
+
"""Test that non-server mode (preview, read-only) restricts diff tools"""
|
|
499
|
+
mock_context = MagicMock(spec=RecceContext)
|
|
500
|
+
server = RecceMCPServer(mock_context, mode=RecceServerMode.preview)
|
|
501
|
+
|
|
502
|
+
# Verify mode is set correctly
|
|
503
|
+
assert server.mode == RecceServerMode.preview
|
|
504
|
+
# Verify it's not server mode
|
|
505
|
+
assert server.mode != RecceServerMode.server
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from recce.pull_request import _fetch_pr_title, fetch_pr_metadata_from_event_path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def mock_github_event(tmp_path, monkeypatch):
|
|
12
|
+
event_data = {
|
|
13
|
+
"number": 1,
|
|
14
|
+
"pull_request": {
|
|
15
|
+
"_links": {
|
|
16
|
+
"html": {"href": "https://github.com/xyz/abc/pull/1"},
|
|
17
|
+
"self": {"href": "api_url_here"},
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
event_file = tmp_path / "github_event.json"
|
|
22
|
+
with open(event_file, "w") as f:
|
|
23
|
+
json.dump(event_data, f)
|
|
24
|
+
|
|
25
|
+
monkeypatch.setenv("GITHUB_EVENT_PATH", str(event_file))
|
|
26
|
+
monkeypatch.setenv("GITHUB_REPOSITORY", "abc/xyz")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def mock_github_token(monkeypatch):
|
|
31
|
+
monkeypatch.setenv("GITHUB_TOKEN", "github_token_here")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MockResponse:
|
|
35
|
+
def __init__(self, status_code, json_data=None):
|
|
36
|
+
self.status_code = status_code
|
|
37
|
+
self.json_data = json_data
|
|
38
|
+
|
|
39
|
+
def json(self):
|
|
40
|
+
return self.json_data
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.fixture
|
|
44
|
+
def mock_get_request_success(monkeypatch):
|
|
45
|
+
def mock_get(*args, **kwargs):
|
|
46
|
+
return MockResponse(200, json_data={"title": "Update README.md"})
|
|
47
|
+
|
|
48
|
+
monkeypatch.setattr(requests, "get", mock_get)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def mock_get_request_failure(monkeypatch):
|
|
53
|
+
def mock_get(*args, **kwargs):
|
|
54
|
+
return MockResponse(404)
|
|
55
|
+
|
|
56
|
+
monkeypatch.setattr(requests, "get", mock_get)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_fetch_pr_metadata(mock_github_event, mock_github_token, mock_get_request_success):
|
|
60
|
+
result = fetch_pr_metadata_from_event_path()
|
|
61
|
+
|
|
62
|
+
assert result is not None
|
|
63
|
+
assert result["github_pr_id"] == 1
|
|
64
|
+
assert result["github_pr_url"] == "https://github.com/xyz/abc/pull/1"
|
|
65
|
+
assert result["github_pr_title"] == "Update README.md"
|
|
66
|
+
assert result["github_repository"] == "abc/xyz"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_fetch_pr_metadata_no_event(mock_get_request_success, monkeypatch):
|
|
70
|
+
monkeypatch.delenv("GITHUB_EVENT_PATH", raising=False)
|
|
71
|
+
|
|
72
|
+
result = fetch_pr_metadata_from_event_path()
|
|
73
|
+
|
|
74
|
+
assert result is None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_fetch_pr_metadata_request_error(mock_github_event, mock_get_request_failure):
|
|
78
|
+
result = fetch_pr_metadata_from_event_path()
|
|
79
|
+
|
|
80
|
+
assert result is not None
|
|
81
|
+
assert result["github_pr_id"] == 1
|
|
82
|
+
assert result["github_pr_url"] == "https://github.com/xyz/abc/pull/1"
|
|
83
|
+
assert result["github_pr_title"] is None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_fetch_pr_metadata_exception(mock_github_event, monkeypatch):
|
|
87
|
+
monkeypatch.setenv("GITHUB_EVENT_PATH", "nonexistent_path")
|
|
88
|
+
|
|
89
|
+
result = fetch_pr_metadata_from_event_path()
|
|
90
|
+
|
|
91
|
+
assert result is None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_fetch_pr_metadata_no_token(mock_github_event):
|
|
95
|
+
with patch.dict("os.environ", clear=True):
|
|
96
|
+
result = fetch_pr_metadata_from_event_path()
|
|
97
|
+
|
|
98
|
+
assert result is None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_fetch_pr_title(mock_github_token, mock_get_request_success):
|
|
102
|
+
result = _fetch_pr_title("api_url_here")
|
|
103
|
+
|
|
104
|
+
assert result == "Update README.md"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_fetch_pr_title_request_error(mock_github_token, mock_get_request_failure):
|
|
108
|
+
result = _fetch_pr_title("api_url_here")
|
|
109
|
+
|
|
110
|
+
assert result is None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_fetch_pr_title_exception(mock_github_token, monkeypatch):
|
|
114
|
+
monkeypatch.setenv("GITHUB_TOKEN", "github_token_here")
|
|
115
|
+
|
|
116
|
+
def mock_get(*args, **kwargs):
|
|
117
|
+
raise Exception("Test exception")
|
|
118
|
+
|
|
119
|
+
monkeypatch.setattr(requests, "get", mock_get)
|
|
120
|
+
|
|
121
|
+
result = _fetch_pr_title("api_url_here")
|
|
122
|
+
|
|
123
|
+
assert result is None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_fetch_pr_title_no_token():
|
|
127
|
+
with patch.dict("os.environ", clear=True):
|
|
128
|
+
result = _fetch_pr_title("api_url_here")
|
|
129
|
+
|
|
130
|
+
assert result is None
|