@useatlas/create 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +231 -0
- package/index.ts +829 -0
- package/package.json +38 -0
- package/templates/docker/.env.example +67 -0
- package/templates/docker/Dockerfile +52 -0
- package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/docker/bin/__tests__/eval.test.ts +434 -0
- package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/docker/bin/__tests__/query.test.ts +240 -0
- package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/docker/bin/atlas.ts +5044 -0
- package/templates/docker/bin/benchmark.ts +695 -0
- package/templates/docker/bin/enrich.ts +559 -0
- package/templates/docker/bin/eval.ts +770 -0
- package/templates/docker/bin/smoke.ts +438 -0
- package/templates/docker/data/.gitkeep +0 -0
- package/templates/docker/data/cybersec.sql +1961 -0
- package/templates/docker/data/demo-semantic/catalog.yml +40 -0
- package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
- package/templates/docker/data/demo-semantic/glossary.yml +22 -0
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/docker/data/demo.sql +373 -0
- package/templates/docker/data/ecommerce.sql +1690 -0
- package/templates/docker/data/init-demo-db.sql +8 -0
- package/templates/docker/docker-compose.yml +34 -0
- package/templates/docker/docs/deploy.md +390 -0
- package/templates/docker/eslint.config.mjs +18 -0
- package/templates/docker/gitignore +5 -0
- package/templates/docker/next.config.ts +9 -0
- package/templates/docker/package.json +59 -0
- package/templates/docker/postcss.config.mjs +8 -0
- package/templates/docker/public/.gitkeep +0 -0
- package/templates/docker/public/favicon.svg +4 -0
- package/templates/docker/railway.json +13 -0
- package/templates/docker/render.yaml +34 -0
- package/templates/docker/semantic/catalog.yml +5 -0
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/glossary.yml +6 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/sidecar/Dockerfile +28 -0
- package/templates/docker/sidecar/railway.json +14 -0
- package/templates/docker/sidecar/server.ts +188 -0
- package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
- package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
- package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
- package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
- package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/docker/src/api/__tests__/health.test.ts +261 -0
- package/templates/docker/src/api/__tests__/query.test.ts +891 -0
- package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
- package/templates/docker/src/api/index.ts +117 -0
- package/templates/docker/src/api/routes/actions.ts +274 -0
- package/templates/docker/src/api/routes/admin.ts +757 -0
- package/templates/docker/src/api/routes/auth.ts +48 -0
- package/templates/docker/src/api/routes/chat.ts +465 -0
- package/templates/docker/src/api/routes/conversations.ts +266 -0
- package/templates/docker/src/api/routes/health.ts +287 -0
- package/templates/docker/src/api/routes/openapi.ts +390 -0
- package/templates/docker/src/api/routes/query.ts +318 -0
- package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/docker/src/api/routes/slack.ts +611 -0
- package/templates/docker/src/api/server.ts +226 -0
- package/templates/docker/src/app/api/[...route]/route.ts +33 -0
- package/templates/docker/src/app/error.tsx +24 -0
- package/templates/docker/src/app/globals.css +126 -0
- package/templates/docker/src/app/layout.tsx +19 -0
- package/templates/docker/src/app/page.tsx +14 -0
- package/templates/docker/src/global.d.ts +1 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/docker/src/lib/action-types.ts +95 -0
- package/templates/docker/src/lib/agent-query.ts +178 -0
- package/templates/docker/src/lib/agent.ts +505 -0
- package/templates/docker/src/lib/api-url.ts +2 -0
- package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/docker/src/lib/auth/audit.ts +89 -0
- package/templates/docker/src/lib/auth/byot.ts +158 -0
- package/templates/docker/src/lib/auth/client.ts +35 -0
- package/templates/docker/src/lib/auth/detect.ts +83 -0
- package/templates/docker/src/lib/auth/managed.ts +73 -0
- package/templates/docker/src/lib/auth/middleware.ts +208 -0
- package/templates/docker/src/lib/auth/migrate.ts +111 -0
- package/templates/docker/src/lib/auth/permissions.ts +156 -0
- package/templates/docker/src/lib/auth/server.ts +142 -0
- package/templates/docker/src/lib/auth/simple-key.ts +92 -0
- package/templates/docker/src/lib/auth/types.ts +49 -0
- package/templates/docker/src/lib/config.ts +704 -0
- package/templates/docker/src/lib/conversation-types.ts +29 -0
- package/templates/docker/src/lib/conversations.ts +270 -0
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/docker/src/lib/db/connection.ts +753 -0
- package/templates/docker/src/lib/db/duckdb.ts +122 -0
- package/templates/docker/src/lib/db/internal.ts +273 -0
- package/templates/docker/src/lib/db/salesforce.ts +342 -0
- package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/docker/src/lib/errors.ts +154 -0
- package/templates/docker/src/lib/logger.ts +98 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/docker/src/lib/plugins/hooks.ts +162 -0
- package/templates/docker/src/lib/plugins/index.ts +9 -0
- package/templates/docker/src/lib/plugins/migrate.ts +309 -0
- package/templates/docker/src/lib/plugins/registry.ts +231 -0
- package/templates/docker/src/lib/plugins/tools.ts +39 -0
- package/templates/docker/src/lib/plugins/wiring.ts +291 -0
- package/templates/docker/src/lib/providers.ts +102 -0
- package/templates/docker/src/lib/rls.ts +321 -0
- package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
- package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
- package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
- package/templates/docker/src/lib/scheduler/engine.ts +317 -0
- package/templates/docker/src/lib/scheduler/executor.ts +73 -0
- package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
- package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/docker/src/lib/scheduler/index.ts +7 -0
- package/templates/docker/src/lib/security.ts +11 -0
- package/templates/docker/src/lib/semantic-index.ts +503 -0
- package/templates/docker/src/lib/semantic.ts +387 -0
- package/templates/docker/src/lib/sidecar-types.ts +16 -0
- package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/docker/src/lib/slack/api.ts +102 -0
- package/templates/docker/src/lib/slack/format.ts +209 -0
- package/templates/docker/src/lib/slack/store.ts +107 -0
- package/templates/docker/src/lib/slack/threads.ts +64 -0
- package/templates/docker/src/lib/slack/verify.ts +71 -0
- package/templates/docker/src/lib/startup.ts +730 -0
- package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
- package/templates/docker/src/lib/tools/actions/email.ts +191 -0
- package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
- package/templates/docker/src/lib/tools/actions/index.ts +23 -0
- package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
- package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/docker/src/lib/tools/explore.ts +379 -0
- package/templates/docker/src/lib/tools/registry.ts +221 -0
- package/templates/docker/src/lib/tools/salesforce.ts +138 -0
- package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
- package/templates/docker/src/lib/tools/sql.ts +680 -0
- package/templates/docker/src/lib/tracing.ts +40 -0
- package/templates/docker/src/lib/utils.ts +6 -0
- package/templates/docker/src/test-setup.ts +38 -0
- package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
- package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/docker/src/ui/context.tsx +78 -0
- package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/docker/src/ui/lib/action-types.ts +63 -0
- package/templates/docker/src/ui/lib/helpers.ts +104 -0
- package/templates/docker/src/ui/lib/types.ts +145 -0
- package/templates/docker/tsconfig.json +41 -0
- package/templates/docker/vercel.json +3 -0
- package/templates/nextjs-standalone/.env.example +68 -0
- package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
- package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
- package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
- package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
- package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
- package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
- package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
- package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
- package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
- package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
- package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
- package/templates/nextjs-standalone/bin/enrich.ts +559 -0
- package/templates/nextjs-standalone/bin/eval.ts +770 -0
- package/templates/nextjs-standalone/bin/smoke.ts +438 -0
- package/templates/nextjs-standalone/data/.gitkeep +0 -0
- package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
- package/templates/nextjs-standalone/data/demo.sql +373 -0
- package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
- package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
- package/templates/nextjs-standalone/docs/deploy.md +390 -0
- package/templates/nextjs-standalone/eslint.config.mjs +18 -0
- package/templates/nextjs-standalone/gitignore +5 -0
- package/templates/nextjs-standalone/next.config.ts +10 -0
- package/templates/nextjs-standalone/package.json +63 -0
- package/templates/nextjs-standalone/postcss.config.mjs +8 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
- package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
- package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
- package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
- package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
- package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
- package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
- package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
- package/templates/nextjs-standalone/src/api/index.ts +117 -0
- package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
- package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
- package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
- package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
- package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
- package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
- package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
- package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
- package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
- package/templates/nextjs-standalone/src/api/server.ts +226 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
- package/templates/nextjs-standalone/src/app/error.tsx +24 -0
- package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
- package/templates/nextjs-standalone/src/app/globals.css +126 -0
- package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
- package/templates/nextjs-standalone/src/app/page.tsx +14 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
- package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
- package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
- package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
- package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
- package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
- package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
- package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
- package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
- package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
- package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
- package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
- package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
- package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
- package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
- package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
- package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
- package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
- package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
- package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
- package/templates/nextjs-standalone/src/lib/config.ts +704 -0
- package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
- package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
- package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
- package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
- package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
- package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
- package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
- package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
- package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
- package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
- package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
- package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
- package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
- package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
- package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
- package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
- package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
- package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
- package/templates/nextjs-standalone/src/lib/security.ts +11 -0
- package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
- package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
- package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
- package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
- package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
- package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
- package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
- package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
- package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
- package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
- package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
- package/templates/nextjs-standalone/src/test-setup.ts +38 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
- package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
- package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
- package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
- package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
- package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
- package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
- package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
- package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
- package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
- package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
- package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
- package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
- package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
- package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
- package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
- package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
- package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
- package/templates/nextjs-standalone/tsconfig.json +32 -0
- package/templates/nextjs-standalone/vercel.json +4 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { validateSOQL, appendSOQLLimit } from "../soql-validation";
|
|
3
|
+
|
|
4
|
+
const ALLOWED = new Set(["Account", "Contact", "Opportunity", "Lead"]);
|
|
5
|
+
|
|
6
|
+
describe("validateSOQL", () => {
|
|
7
|
+
describe("Layer 0: Empty check", () => {
|
|
8
|
+
it("rejects empty string", () => {
|
|
9
|
+
const result = validateSOQL("", ALLOWED);
|
|
10
|
+
expect(result.valid).toBe(false);
|
|
11
|
+
expect(result.error).toContain("Empty");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("rejects whitespace-only", () => {
|
|
15
|
+
const result = validateSOQL(" \n\t ", ALLOWED);
|
|
16
|
+
expect(result.valid).toBe(false);
|
|
17
|
+
expect(result.error).toContain("Empty");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("Layer 1: Mutation guard", () => {
|
|
22
|
+
for (const keyword of ["INSERT", "UPDATE", "DELETE", "UPSERT", "MERGE", "UNDELETE"]) {
|
|
23
|
+
it(`rejects ${keyword}`, () => {
|
|
24
|
+
const result = validateSOQL(`${keyword} INTO Account`, ALLOWED);
|
|
25
|
+
expect(result.valid).toBe(false);
|
|
26
|
+
expect(result.error).toContain("Forbidden");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it(`rejects ${keyword.toLowerCase()}`, () => {
|
|
30
|
+
const result = validateSOQL(`${keyword.toLowerCase()} into account`, ALLOWED);
|
|
31
|
+
expect(result.valid).toBe(false);
|
|
32
|
+
expect(result.error).toContain("Forbidden");
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("Layer 2: SELECT-only", () => {
|
|
38
|
+
it("accepts SELECT query", () => {
|
|
39
|
+
const result = validateSOQL("SELECT Id FROM Account", ALLOWED);
|
|
40
|
+
expect(result.valid).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("rejects non-SELECT query", () => {
|
|
44
|
+
const result = validateSOQL("DESCRIBE Account", ALLOWED);
|
|
45
|
+
expect(result.valid).toBe(false);
|
|
46
|
+
expect(result.error).toContain("Only SELECT");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("rejects semicolons", () => {
|
|
50
|
+
const result = validateSOQL("SELECT Id FROM Account;", ALLOWED);
|
|
51
|
+
expect(result.valid).toBe(false);
|
|
52
|
+
expect(result.error).toContain("Semicolons");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("rejects multiple statements", () => {
|
|
56
|
+
const result = validateSOQL("SELECT Id FROM Account; SELECT Id FROM Contact", ALLOWED);
|
|
57
|
+
expect(result.valid).toBe(false);
|
|
58
|
+
expect(result.error).toContain("Semicolons");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("Layer 3: Object whitelist", () => {
|
|
63
|
+
it("allows whitelisted objects", () => {
|
|
64
|
+
const result = validateSOQL("SELECT Id, Name FROM Account", ALLOWED);
|
|
65
|
+
expect(result.valid).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("rejects non-whitelisted objects", () => {
|
|
69
|
+
const result = validateSOQL("SELECT Id FROM CustomObject__c", ALLOWED);
|
|
70
|
+
expect(result.valid).toBe(false);
|
|
71
|
+
expect(result.error).toContain("not in the allowed list");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("checks subquery objects", () => {
|
|
75
|
+
const result = validateSOQL(
|
|
76
|
+
"SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM CustomObject__c)",
|
|
77
|
+
ALLOWED,
|
|
78
|
+
);
|
|
79
|
+
expect(result.valid).toBe(false);
|
|
80
|
+
expect(result.error).toContain("CustomObject__c");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("allows subquery with whitelisted objects", () => {
|
|
84
|
+
const result = validateSOQL(
|
|
85
|
+
"SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM Contact)",
|
|
86
|
+
ALLOWED,
|
|
87
|
+
);
|
|
88
|
+
expect(result.valid).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("is case-insensitive", () => {
|
|
92
|
+
const result = validateSOQL("SELECT Id FROM account", ALLOWED);
|
|
93
|
+
expect(result.valid).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("rejects queries with no FROM clause", () => {
|
|
97
|
+
const result = validateSOQL("SELECT 1", ALLOWED);
|
|
98
|
+
expect(result.valid).toBe(false);
|
|
99
|
+
expect(result.error).toContain("No FROM");
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("Relationship subquery whitelist bypass (parent-to-child)", () => {
|
|
104
|
+
it("accepts parent-to-child relationship subquery with plural relationship name", () => {
|
|
105
|
+
// "Contacts" is the relationship name (plural), not in the whitelist.
|
|
106
|
+
// Only "Contact" (singular) is whitelisted. This should pass because
|
|
107
|
+
// relationship subqueries in SELECT are not whitelist-checked.
|
|
108
|
+
const result = validateSOQL(
|
|
109
|
+
"SELECT Id, Name, (SELECT LastName FROM Contacts) FROM Account",
|
|
110
|
+
ALLOWED,
|
|
111
|
+
);
|
|
112
|
+
expect(result.valid).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("accepts multiple relationship subqueries in SELECT", () => {
|
|
116
|
+
const result = validateSOQL(
|
|
117
|
+
"SELECT Id, (SELECT LastName FROM Contacts), (SELECT Amount FROM Opportunities) FROM Account",
|
|
118
|
+
ALLOWED,
|
|
119
|
+
);
|
|
120
|
+
expect(result.valid).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("accepts relationship subquery with unknown relationship name", () => {
|
|
124
|
+
// Custom relationship names like "Cases" won't be in the whitelist
|
|
125
|
+
const result = validateSOQL(
|
|
126
|
+
"SELECT Id, (SELECT Subject FROM Cases) FROM Account",
|
|
127
|
+
ALLOWED,
|
|
128
|
+
);
|
|
129
|
+
expect(result.valid).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("still rejects non-whitelisted objects in WHERE semi-join subqueries", () => {
|
|
133
|
+
// Semi-join subqueries in WHERE reference real object names — must be checked
|
|
134
|
+
const result = validateSOQL(
|
|
135
|
+
"SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM CustomObject__c)",
|
|
136
|
+
ALLOWED,
|
|
137
|
+
);
|
|
138
|
+
expect(result.valid).toBe(false);
|
|
139
|
+
expect(result.error).toContain("CustomObject__c");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("allows whitelisted objects in WHERE semi-join subqueries", () => {
|
|
143
|
+
const result = validateSOQL(
|
|
144
|
+
"SELECT Id FROM Account WHERE Id IN (SELECT AccountId FROM Contact)",
|
|
145
|
+
ALLOWED,
|
|
146
|
+
);
|
|
147
|
+
expect(result.valid).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("accepts relationship subquery AND valid WHERE subquery together", () => {
|
|
151
|
+
const result = validateSOQL(
|
|
152
|
+
"SELECT Id, (SELECT LastName FROM Contacts) FROM Account WHERE Id IN (SELECT AccountId FROM Opportunity)",
|
|
153
|
+
ALLOWED,
|
|
154
|
+
);
|
|
155
|
+
expect(result.valid).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("rejects relationship subquery with invalid WHERE subquery", () => {
|
|
159
|
+
const result = validateSOQL(
|
|
160
|
+
"SELECT Id, (SELECT LastName FROM Contacts) FROM Account WHERE Id IN (SELECT AccountId FROM Forbidden__c)",
|
|
161
|
+
ALLOWED,
|
|
162
|
+
);
|
|
163
|
+
expect(result.valid).toBe(false);
|
|
164
|
+
expect(result.error).toContain("Forbidden__c");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("still checks top-level FROM object", () => {
|
|
168
|
+
const result = validateSOQL(
|
|
169
|
+
"SELECT Id, (SELECT LastName FROM Contacts) FROM NotAllowed__c",
|
|
170
|
+
ALLOWED,
|
|
171
|
+
);
|
|
172
|
+
expect(result.valid).toBe(false);
|
|
173
|
+
expect(result.error).toContain("NotAllowed__c");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("String literal false positives in mutation guard", () => {
|
|
178
|
+
it("allows 'delete' inside a string literal", () => {
|
|
179
|
+
const result = validateSOQL(
|
|
180
|
+
"SELECT Id FROM Account WHERE Name = 'delete this'",
|
|
181
|
+
ALLOWED,
|
|
182
|
+
);
|
|
183
|
+
expect(result.valid).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("allows 'update' inside a string literal", () => {
|
|
187
|
+
const result = validateSOQL(
|
|
188
|
+
"SELECT Id FROM Account WHERE Description = 'please update record'",
|
|
189
|
+
ALLOWED,
|
|
190
|
+
);
|
|
191
|
+
expect(result.valid).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("allows 'insert' inside a string literal", () => {
|
|
195
|
+
const result = validateSOQL(
|
|
196
|
+
"SELECT Id FROM Contact WHERE Name = 'insert coin'",
|
|
197
|
+
ALLOWED,
|
|
198
|
+
);
|
|
199
|
+
expect(result.valid).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("allows 'merge' inside a string literal", () => {
|
|
203
|
+
const result = validateSOQL(
|
|
204
|
+
"SELECT Id FROM Lead WHERE Status = 'merge pending'",
|
|
205
|
+
ALLOWED,
|
|
206
|
+
);
|
|
207
|
+
expect(result.valid).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("allows 'upsert' inside a string literal", () => {
|
|
211
|
+
const result = validateSOQL(
|
|
212
|
+
"SELECT Id FROM Account WHERE Name = 'upsert test'",
|
|
213
|
+
ALLOWED,
|
|
214
|
+
);
|
|
215
|
+
expect(result.valid).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("allows LIKE pattern with forbidden keyword", () => {
|
|
219
|
+
const result = validateSOQL(
|
|
220
|
+
"SELECT Id FROM Account WHERE Name LIKE '%delete%'",
|
|
221
|
+
ALLOWED,
|
|
222
|
+
);
|
|
223
|
+
expect(result.valid).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("still rejects actual DELETE statements", () => {
|
|
227
|
+
const result = validateSOQL("DELETE FROM Account", ALLOWED);
|
|
228
|
+
expect(result.valid).toBe(false);
|
|
229
|
+
expect(result.error).toContain("Forbidden");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("still rejects forbidden keyword outside string literal even with strings present", () => {
|
|
233
|
+
// The keyword DELETE appears outside the string
|
|
234
|
+
const result = validateSOQL(
|
|
235
|
+
"DELETE FROM Account WHERE Name = 'safe string'",
|
|
236
|
+
ALLOWED,
|
|
237
|
+
);
|
|
238
|
+
expect(result.valid).toBe(false);
|
|
239
|
+
expect(result.error).toContain("Forbidden");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("handles multiple string literals with forbidden keywords", () => {
|
|
243
|
+
const result = validateSOQL(
|
|
244
|
+
"SELECT Id FROM Account WHERE Name = 'delete' AND Type = 'update this'",
|
|
245
|
+
ALLOWED,
|
|
246
|
+
);
|
|
247
|
+
expect(result.valid).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("handles empty string literals", () => {
|
|
251
|
+
const result = validateSOQL(
|
|
252
|
+
"SELECT Id FROM Account WHERE Name = ''",
|
|
253
|
+
ALLOWED,
|
|
254
|
+
);
|
|
255
|
+
expect(result.valid).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("valid queries", () => {
|
|
260
|
+
it("accepts basic query", () => {
|
|
261
|
+
const result = validateSOQL("SELECT Id, Name FROM Account LIMIT 10", ALLOWED);
|
|
262
|
+
expect(result.valid).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("accepts query with WHERE clause", () => {
|
|
266
|
+
const result = validateSOQL(
|
|
267
|
+
"SELECT Id, Name FROM Account WHERE Name = 'Test'",
|
|
268
|
+
ALLOWED,
|
|
269
|
+
);
|
|
270
|
+
expect(result.valid).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("accepts query with aggregate functions", () => {
|
|
274
|
+
const result = validateSOQL(
|
|
275
|
+
"SELECT COUNT(Id) FROM Opportunity GROUP BY StageName",
|
|
276
|
+
ALLOWED,
|
|
277
|
+
);
|
|
278
|
+
expect(result.valid).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("appendSOQLLimit", () => {
|
|
284
|
+
it("appends LIMIT when not present", () => {
|
|
285
|
+
const result = appendSOQLLimit("SELECT Id FROM Account", 100);
|
|
286
|
+
expect(result).toBe("SELECT Id FROM Account LIMIT 100");
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("does not append LIMIT when already present", () => {
|
|
290
|
+
const result = appendSOQLLimit("SELECT Id FROM Account LIMIT 50", 100);
|
|
291
|
+
expect(result).toBe("SELECT Id FROM Account LIMIT 50");
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("is case-insensitive for existing LIMIT", () => {
|
|
295
|
+
const result = appendSOQLLimit("SELECT Id FROM Account limit 50", 100);
|
|
296
|
+
expect(result).toBe("SELECT Id FROM Account limit 50");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("trims whitespace", () => {
|
|
300
|
+
const result = appendSOQLLimit(" SELECT Id FROM Account ", 100);
|
|
301
|
+
expect(result).toBe("SELECT Id FROM Account LIMIT 100");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests: executeSQL → logQueryAudit → internal DB.
|
|
3
|
+
*
|
|
4
|
+
* Tests the full audit path without mocking @/lib/auth/audit or @/lib/logger.
|
|
5
|
+
* Instead, uses _resetPool() to inject a mock pg.Pool that captures INSERT
|
|
6
|
+
* queries from internalExecute. This avoids bun mock.module leaking across
|
|
7
|
+
* test files.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it, beforeEach, afterEach, mock, type Mock } from "bun:test";
|
|
11
|
+
import { _resetPool, type InternalPool } from "@atlas/api/lib/db/internal";
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
type AnyResult = any;
|
|
15
|
+
|
|
16
|
+
// --- Mocks for sql.ts dependencies (NOT audit or logger) ---
|
|
17
|
+
|
|
18
|
+
mock.module("@atlas/api/lib/semantic", () => ({
|
|
19
|
+
getWhitelistedTables: () => new Set(["companies", "people"]),
|
|
20
|
+
_resetWhitelists: () => {},
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
let queryFn: Mock<(sql: string, timeout: number) => Promise<{ columns: string[]; rows: Record<string, unknown>[] }>>;
|
|
24
|
+
|
|
25
|
+
const mockConn = {
|
|
26
|
+
query: (...args: [string, number]) => queryFn(...args),
|
|
27
|
+
close: async () => {},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
mock.module("@atlas/api/lib/db/connection", () => ({
|
|
31
|
+
getDB: () => mockConn,
|
|
32
|
+
connections: {
|
|
33
|
+
get: () => mockConn,
|
|
34
|
+
getDefault: () => mockConn,
|
|
35
|
+
getDBType: () => "postgres",
|
|
36
|
+
getTargetHost: () => "localhost",
|
|
37
|
+
getValidator: () => undefined,
|
|
38
|
+
list: () => ["default"],
|
|
39
|
+
},
|
|
40
|
+
detectDBType: () => "postgres",
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
mock.module("@atlas/api/lib/tracing", () => ({
|
|
44
|
+
withSpan: async (
|
|
45
|
+
_name: string,
|
|
46
|
+
_attrs: Record<string, unknown>,
|
|
47
|
+
fn: () => Promise<unknown>,
|
|
48
|
+
) => fn(),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
mock.module("@atlas/api/lib/db/source-rate-limit", () => ({
|
|
52
|
+
acquireSourceSlot: () => ({ acquired: true }),
|
|
53
|
+
decrementSourceConcurrency: () => {},
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Import after mocks
|
|
57
|
+
const { executeSQL } = await import("@atlas/api/lib/tools/sql");
|
|
58
|
+
|
|
59
|
+
// --- Internal DB mock pool to capture audit inserts ---
|
|
60
|
+
|
|
61
|
+
let auditInserts: Array<{ sql: string; params?: unknown[] }> = [];
|
|
62
|
+
|
|
63
|
+
const mockPool: InternalPool = {
|
|
64
|
+
query: async (sql: string, params?: unknown[]) => {
|
|
65
|
+
auditInserts.push({ sql, params });
|
|
66
|
+
return { rows: [] };
|
|
67
|
+
},
|
|
68
|
+
end: async () => {},
|
|
69
|
+
on: () => {},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Audit INSERT parameters are positional ($1-$8).
|
|
74
|
+
* This helper extracts named fields from the params array so tests
|
|
75
|
+
* are not coupled to column ordering.
|
|
76
|
+
*/
|
|
77
|
+
function extractAuditParams(params: unknown[]) {
|
|
78
|
+
return {
|
|
79
|
+
userId: params[0],
|
|
80
|
+
userLabel: params[1],
|
|
81
|
+
authMode: params[2],
|
|
82
|
+
sql: params[3] as string,
|
|
83
|
+
durationMs: params[4] as number,
|
|
84
|
+
rowCount: params[5] as number | null,
|
|
85
|
+
success: params[6] as boolean,
|
|
86
|
+
error: params[7] as string | null,
|
|
87
|
+
sourceId: params[8] as string | null,
|
|
88
|
+
sourceType: params[9] as string | null,
|
|
89
|
+
targetHost: params[10] as string | null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("executeSQL audit logging", () => {
|
|
94
|
+
const origDbUrl = process.env.DATABASE_URL;
|
|
95
|
+
const origDatasource = process.env.ATLAS_DATASOURCE_URL;
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
auditInserts = [];
|
|
99
|
+
process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
|
|
100
|
+
// Enable internal DB so audit inserts are captured
|
|
101
|
+
process.env.DATABASE_URL = "postgresql://test:test@localhost:5432/atlas";
|
|
102
|
+
_resetPool(mockPool);
|
|
103
|
+
queryFn = mock(() =>
|
|
104
|
+
Promise.resolve({
|
|
105
|
+
columns: ["id", "name"],
|
|
106
|
+
rows: [{ id: 1, name: "Acme" }],
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
afterEach(() => {
|
|
112
|
+
if (origDbUrl) {
|
|
113
|
+
process.env.DATABASE_URL = origDbUrl;
|
|
114
|
+
} else {
|
|
115
|
+
delete process.env.DATABASE_URL;
|
|
116
|
+
}
|
|
117
|
+
if (origDatasource) {
|
|
118
|
+
process.env.ATLAS_DATASOURCE_URL = origDatasource;
|
|
119
|
+
} else {
|
|
120
|
+
delete process.env.ATLAS_DATASOURCE_URL;
|
|
121
|
+
}
|
|
122
|
+
_resetPool(null);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const exec = (sql: string) =>
|
|
126
|
+
executeSQL.execute!(
|
|
127
|
+
{ sql, explanation: "test" },
|
|
128
|
+
{ toolCallId: "test", messages: [], abortSignal: undefined as never },
|
|
129
|
+
) as Promise<AnyResult>;
|
|
130
|
+
|
|
131
|
+
/** Find audit INSERT queries from the mock pool */
|
|
132
|
+
const getAuditInserts = () =>
|
|
133
|
+
auditInserts.filter((q) => q.sql.includes("INSERT INTO audit_log"));
|
|
134
|
+
|
|
135
|
+
it("logs audit with success: true on successful query", async () => {
|
|
136
|
+
const result = await exec("SELECT id, name FROM companies");
|
|
137
|
+
|
|
138
|
+
expect(result.success).toBe(true);
|
|
139
|
+
const inserts = getAuditInserts();
|
|
140
|
+
expect(inserts).toHaveLength(1);
|
|
141
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
142
|
+
expect(audit.success).toBe(true);
|
|
143
|
+
expect(audit.sql).toContain("SELECT id, name FROM companies");
|
|
144
|
+
expect(audit.rowCount).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("logs audit with success: false on query execution failure", async () => {
|
|
148
|
+
queryFn = mock(() => Promise.reject(new Error("column \"nope\" does not exist")));
|
|
149
|
+
|
|
150
|
+
const result = await exec("SELECT nope FROM companies");
|
|
151
|
+
|
|
152
|
+
expect(result.success).toBe(false);
|
|
153
|
+
const inserts = getAuditInserts();
|
|
154
|
+
expect(inserts).toHaveLength(1);
|
|
155
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
156
|
+
expect(audit.success).toBe(false);
|
|
157
|
+
expect(audit.error).toContain("column \"nope\" does not exist");
|
|
158
|
+
expect(audit.rowCount).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("logs audit with 'Validation rejected:' prefix on validation failure", async () => {
|
|
162
|
+
const result = await exec("DROP TABLE companies");
|
|
163
|
+
|
|
164
|
+
expect(result.success).toBe(false);
|
|
165
|
+
const inserts = getAuditInserts();
|
|
166
|
+
expect(inserts).toHaveLength(1);
|
|
167
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
168
|
+
expect(audit.success).toBe(false);
|
|
169
|
+
expect((audit.error as string).startsWith("Validation rejected:")).toBe(true);
|
|
170
|
+
expect(audit.durationMs).toBe(0);
|
|
171
|
+
expect(audit.rowCount).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("logs audit for non-whitelisted table access attempt", async () => {
|
|
175
|
+
const result = await exec("SELECT * FROM unknown_table");
|
|
176
|
+
|
|
177
|
+
expect(result.success).toBe(false);
|
|
178
|
+
const inserts = getAuditInserts();
|
|
179
|
+
expect(inserts).toHaveLength(1);
|
|
180
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
181
|
+
expect(audit.success).toBe(false);
|
|
182
|
+
expect(audit.error).toContain("Validation rejected:");
|
|
183
|
+
expect(audit.error).toContain("not in the allowed list");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("truncates SQL to 2000 chars in validation-rejected audit entries", async () => {
|
|
187
|
+
const longSql = "DROP TABLE " + "x".repeat(3000);
|
|
188
|
+
await exec(longSql);
|
|
189
|
+
|
|
190
|
+
const inserts = getAuditInserts();
|
|
191
|
+
expect(inserts).toHaveLength(1);
|
|
192
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
193
|
+
expect(audit.sql.length).toBeLessThanOrEqual(2000);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("auto-appends LIMIT to the SQL logged on success", async () => {
|
|
197
|
+
await exec("SELECT id FROM companies");
|
|
198
|
+
|
|
199
|
+
const inserts = getAuditInserts();
|
|
200
|
+
expect(inserts).toHaveLength(1);
|
|
201
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
202
|
+
expect(audit.sql).toContain("LIMIT");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("includes sourceId and targetHost in audit entries on success", async () => {
|
|
206
|
+
await exec("SELECT id FROM companies");
|
|
207
|
+
|
|
208
|
+
const inserts = getAuditInserts();
|
|
209
|
+
expect(inserts).toHaveLength(1);
|
|
210
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
211
|
+
expect(audit.sourceId).toBe("default");
|
|
212
|
+
expect(audit.sourceType).toBe("postgres");
|
|
213
|
+
expect(audit.targetHost).toBe("localhost");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("includes sourceId in validation-rejected audit entries", async () => {
|
|
217
|
+
await exec("DROP TABLE companies");
|
|
218
|
+
|
|
219
|
+
const inserts = getAuditInserts();
|
|
220
|
+
expect(inserts).toHaveLength(1);
|
|
221
|
+
const audit = extractAuditParams(inserts[0].params!);
|
|
222
|
+
expect(audit.sourceId).toBe("default");
|
|
223
|
+
expect(audit.sourceType).toBe("postgres");
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for per-connection table whitelist enforcement in validateSQL.
|
|
3
|
+
*
|
|
4
|
+
* Separated from sql.test.ts because it needs a different mock for
|
|
5
|
+
* getWhitelistedTables that respects the connectionId parameter.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, mock } from "bun:test";
|
|
8
|
+
|
|
9
|
+
// Mock getWhitelistedTables to return different sets per connectionId
|
|
10
|
+
mock.module("@atlas/api/lib/semantic", () => ({
|
|
11
|
+
getWhitelistedTables: (connectionId?: string) => {
|
|
12
|
+
switch (connectionId) {
|
|
13
|
+
case "warehouse":
|
|
14
|
+
return new Set(["events", "analytics.events"]);
|
|
15
|
+
case "nonexistent":
|
|
16
|
+
return new Set(); // empty — unknown connection
|
|
17
|
+
default:
|
|
18
|
+
return new Set(["orders", "users", "companies"]);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
_resetWhitelists: () => {},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// Mock the DB connection — validateSQL doesn't need it, but the module
|
|
25
|
+
// imports it at the top level.
|
|
26
|
+
mock.module("@atlas/api/lib/db/connection", () => ({
|
|
27
|
+
getDB: () => ({
|
|
28
|
+
query: async () => ({ columns: [], rows: [] }),
|
|
29
|
+
close: async () => {},
|
|
30
|
+
}),
|
|
31
|
+
connections: {
|
|
32
|
+
get: () => ({
|
|
33
|
+
query: async () => ({ columns: [], rows: [] }),
|
|
34
|
+
close: async () => {},
|
|
35
|
+
}),
|
|
36
|
+
getDefault: () => ({
|
|
37
|
+
query: async () => ({ columns: [], rows: [] }),
|
|
38
|
+
close: async () => {},
|
|
39
|
+
}),
|
|
40
|
+
getDBType: (id?: string) => {
|
|
41
|
+
if (id === "nonexistent") throw new Error(`Connection "nonexistent" is not registered.`);
|
|
42
|
+
return "postgres" as const;
|
|
43
|
+
},
|
|
44
|
+
getValidator: () => undefined,
|
|
45
|
+
list: () => ["default", "warehouse"],
|
|
46
|
+
describe: () => [
|
|
47
|
+
{ id: "default", dbType: "postgres" as const },
|
|
48
|
+
{ id: "warehouse", dbType: "postgres" as const },
|
|
49
|
+
],
|
|
50
|
+
_reset: () => {},
|
|
51
|
+
},
|
|
52
|
+
detectDBType: () => "postgres" as const,
|
|
53
|
+
ConnectionRegistry: class {},
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const { validateSQL } = await import("../sql");
|
|
57
|
+
|
|
58
|
+
describe("per-connection whitelist enforcement", () => {
|
|
59
|
+
it("allows table in default connection whitelist", () => {
|
|
60
|
+
const result = validateSQL("SELECT * FROM orders");
|
|
61
|
+
expect(result.valid).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("allows table in warehouse connection whitelist", () => {
|
|
65
|
+
const result = validateSQL("SELECT * FROM events", "warehouse");
|
|
66
|
+
expect(result.valid).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("rejects table not in target connection whitelist", () => {
|
|
70
|
+
const result = validateSQL("SELECT * FROM orders", "warehouse");
|
|
71
|
+
expect(result.valid).toBe(false);
|
|
72
|
+
expect(result.error).toContain("not in the allowed list");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("rejects all tables for unknown connection", () => {
|
|
76
|
+
// getDBType throws for "nonexistent", so validateSQL returns an error
|
|
77
|
+
const result = validateSQL("SELECT * FROM orders", "nonexistent");
|
|
78
|
+
expect(result.valid).toBe(false);
|
|
79
|
+
expect(result.error).toContain("not registered");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("default connection cannot access warehouse-only tables", () => {
|
|
83
|
+
const result = validateSQL("SELECT * FROM events");
|
|
84
|
+
expect(result.valid).toBe(false);
|
|
85
|
+
expect(result.error).toContain("not in the allowed list");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("allows schema-qualified table in warehouse whitelist", () => {
|
|
89
|
+
const result = validateSQL("SELECT * FROM analytics.events", "warehouse");
|
|
90
|
+
expect(result.valid).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("rejects schema-qualified table not in warehouse whitelist", () => {
|
|
94
|
+
const result = validateSQL("SELECT * FROM public.orders", "warehouse");
|
|
95
|
+
expect(result.valid).toBe(false);
|
|
96
|
+
expect(result.error).toContain("not in the allowed list");
|
|
97
|
+
});
|
|
98
|
+
});
|