@libredb/studio 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. package/.claude/settings.local.json +127 -0
  2. package/.cursorrules +426 -0
  3. package/.devin/wiki.json +143 -0
  4. package/.dockerignore +80 -0
  5. package/.env.example +159 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
  9. package/.github/workflows/ci.yml +185 -0
  10. package/.github/workflows/codeql.yml +57 -0
  11. package/.github/workflows/docker-build-push.yml +118 -0
  12. package/.github/workflows/helm-release.yml +113 -0
  13. package/CLAUDE.md +265 -0
  14. package/CODE_OF_CONDUCT.md +124 -0
  15. package/CONTRIBUTING.md +154 -0
  16. package/Dockerfile +73 -0
  17. package/LICENSE +21 -0
  18. package/README.md +614 -0
  19. package/SECURITY.md +107 -0
  20. package/artifacthub-repo.yml +4 -0
  21. package/bun.lock +1714 -0
  22. package/bunfig.toml +3 -0
  23. package/charts/libredb-studio/.helmignore +11 -0
  24. package/charts/libredb-studio/Chart.lock +6 -0
  25. package/charts/libredb-studio/Chart.yaml +50 -0
  26. package/charts/libredb-studio/README.md +206 -0
  27. package/charts/libredb-studio/templates/NOTES.txt +59 -0
  28. package/charts/libredb-studio/templates/_helpers.tpl +135 -0
  29. package/charts/libredb-studio/templates/configmap.yaml +37 -0
  30. package/charts/libredb-studio/templates/deployment.yaml +184 -0
  31. package/charts/libredb-studio/templates/hpa.yaml +32 -0
  32. package/charts/libredb-studio/templates/ingress.yaml +41 -0
  33. package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
  34. package/charts/libredb-studio/templates/pdb.yaml +18 -0
  35. package/charts/libredb-studio/templates/pvc.yaml +23 -0
  36. package/charts/libredb-studio/templates/secret.yaml +30 -0
  37. package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
  38. package/charts/libredb-studio/templates/service.yaml +22 -0
  39. package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
  40. package/charts/libredb-studio/values.schema.json +246 -0
  41. package/charts/libredb-studio/values.yaml +286 -0
  42. package/components.json +22 -0
  43. package/conductor/code_styleguides/typescript.md +43 -0
  44. package/conductor/product-guidelines.md +43 -0
  45. package/conductor/product.md +3 -0
  46. package/conductor/setup_state.json +1 -0
  47. package/conductor/tech-stack.md +39 -0
  48. package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
  49. package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
  50. package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
  51. package/conductor/tracks.md +8 -0
  52. package/conductor/workflow.md +333 -0
  53. package/database-compose.yml +55 -0
  54. package/docker/postgres-init/01-extensions.sql +10 -0
  55. package/docker/postgres-init/02-sample-data.sql +585 -0
  56. package/docker/postgres.yml +68 -0
  57. package/docker-compose.yml +38 -0
  58. package/docs/AI_PLAN.md +74 -0
  59. package/docs/API_DOCS.md +875 -0
  60. package/docs/ARCHITECTURE.md +218 -0
  61. package/docs/DATABASE_PROVIDERS.md +358 -0
  62. package/docs/FEATURES.md +116 -0
  63. package/docs/HELM_CHART.md +252 -0
  64. package/docs/LOGIN_PAGE.md +178 -0
  65. package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
  66. package/docs/OIDC_ARCH.md +681 -0
  67. package/docs/OIDC_SETUP.md +322 -0
  68. package/docs/POSTGRES_METRICS.md +516 -0
  69. package/docs/QUERY_OPTIMIZATION.md +370 -0
  70. package/docs/SEED_CONNECTIONS.md +468 -0
  71. package/docs/SQL_ALIAS_COMPLETION.md +190 -0
  72. package/docs/STORAGE_ARCHITECTURE.md +565 -0
  73. package/docs/STORAGE_QUICK_SETUP.md +419 -0
  74. package/docs/TECHNICAL_PLAN.md +36 -0
  75. package/docs/THEMING.md +345 -0
  76. package/docs/adding-a-new-database-provider.md +642 -0
  77. package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
  78. package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
  79. package/docs/backlogs/002-DATA_IMPORT.md +215 -0
  80. package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
  81. package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
  82. package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
  83. package/docs/backlogs/006-DATA_MASKING.md +418 -0
  84. package/docs/enterprise-features.md +718 -0
  85. package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
  86. package/docs/medium-koyeb-article-en.md +215 -0
  87. package/docs/plans/test-plans.md +445 -0
  88. package/docs/releases/RELEASE.V0.3.0.md +22 -0
  89. package/docs/releases/RELEASE.V0.4.0.md +154 -0
  90. package/docs/releases/RELEASE.V0.5.0.md +252 -0
  91. package/docs/releases/RELEASE_v0.5.6.md +145 -0
  92. package/docs/releases/RELEASE_v0.6.1.md +303 -0
  93. package/docs/releases/RELEASE_v0.6.7.md +292 -0
  94. package/docs/releases/RELEASE_v0.7.0.md +332 -0
  95. package/docs/releases/RELEASE_v0.8.0.md +521 -0
  96. package/docs/sampledb/titanic.sql +1379 -0
  97. package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
  98. package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
  99. package/e2e/admin-dashboard.spec.ts +64 -0
  100. package/e2e/connection-management.spec.ts +58 -0
  101. package/e2e/export.spec.ts +34 -0
  102. package/e2e/login.spec.ts +85 -0
  103. package/e2e/query-execution.spec.ts +35 -0
  104. package/e2e/tab-management.spec.ts +64 -0
  105. package/eslint.config.mjs +28 -0
  106. package/fly.toml +43 -0
  107. package/next.config.ts +32 -0
  108. package/package.json +130 -0
  109. package/playwright.config.ts +34 -0
  110. package/postcss.config.mjs +7 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/file.svg +1 -0
  114. package/public/globe.svg +1 -0
  115. package/public/logo.svg +32 -0
  116. package/public/next.svg +1 -0
  117. package/public/screenshots/code-generator.png +0 -0
  118. package/public/screenshots/connection-modal.png +0 -0
  119. package/public/screenshots/data-profiler.png +0 -0
  120. package/public/screenshots/erd-diagram.png +0 -0
  121. package/public/screenshots/hero-editor.png +0 -0
  122. package/public/screenshots/nl2sql.png +0 -0
  123. package/public/vercel.svg +1 -0
  124. package/public/window.svg +1 -0
  125. package/render.yaml +58 -0
  126. package/scripts/merge-lcov.mjs +239 -0
  127. package/sonar-project.properties +16 -0
  128. package/src/app/admin/error.tsx +46 -0
  129. package/src/app/admin/page.tsx +10 -0
  130. package/src/app/api/admin/audit/route.ts +52 -0
  131. package/src/app/api/admin/fleet-health/route.ts +81 -0
  132. package/src/app/api/ai/autopilot/route.ts +105 -0
  133. package/src/app/api/ai/chat/route.ts +132 -0
  134. package/src/app/api/ai/describe-schema/route.ts +52 -0
  135. package/src/app/api/ai/explain/route.ts +86 -0
  136. package/src/app/api/ai/impact/route.ts +97 -0
  137. package/src/app/api/ai/index-advisor/route.ts +98 -0
  138. package/src/app/api/ai/nl2sql/route.ts +87 -0
  139. package/src/app/api/ai/query-safety/route.ts +87 -0
  140. package/src/app/api/auth/login/route.ts +62 -0
  141. package/src/app/api/auth/logout/route.ts +25 -0
  142. package/src/app/api/auth/me/route.ts +10 -0
  143. package/src/app/api/auth/oidc/callback/route.ts +82 -0
  144. package/src/app/api/auth/oidc/login/route.ts +43 -0
  145. package/src/app/api/connections/managed/route.ts +35 -0
  146. package/src/app/api/db/cancel/route.ts +42 -0
  147. package/src/app/api/db/disconnect/route.ts +28 -0
  148. package/src/app/api/db/health/route.ts +49 -0
  149. package/src/app/api/db/maintenance/route.ts +72 -0
  150. package/src/app/api/db/monitoring/route.ts +62 -0
  151. package/src/app/api/db/multi-query/route.ts +116 -0
  152. package/src/app/api/db/pool-stats/route.ts +37 -0
  153. package/src/app/api/db/profile/route.ts +144 -0
  154. package/src/app/api/db/provider-meta/route.ts +49 -0
  155. package/src/app/api/db/query/route.ts +50 -0
  156. package/src/app/api/db/schema/route.ts +47 -0
  157. package/src/app/api/db/schema-snapshot/route.ts +42 -0
  158. package/src/app/api/db/test-connection/route.ts +55 -0
  159. package/src/app/api/db/transaction/route.ts +111 -0
  160. package/src/app/api/storage/[collection]/route.ts +67 -0
  161. package/src/app/api/storage/config/route.ts +17 -0
  162. package/src/app/api/storage/migrate/route.ts +45 -0
  163. package/src/app/api/storage/route.ts +32 -0
  164. package/src/app/error.tsx +49 -0
  165. package/src/app/global-error.tsx +55 -0
  166. package/src/app/globals.css +146 -0
  167. package/src/app/icon.svg +42 -0
  168. package/src/app/layout.tsx +34 -0
  169. package/src/app/login/login-form.tsx +301 -0
  170. package/src/app/login/page.tsx +11 -0
  171. package/src/app/monitoring/page.tsx +8 -0
  172. package/src/app/not-found.tsx +29 -0
  173. package/src/app/page.tsx +5 -0
  174. package/src/components/AIAutopilotPanel.tsx +238 -0
  175. package/src/components/CodeGenerator.tsx +271 -0
  176. package/src/components/CommandPalette.tsx +227 -0
  177. package/src/components/ConnectionModal.tsx +759 -0
  178. package/src/components/CreateTableModal.tsx +281 -0
  179. package/src/components/DataCharts.tsx +962 -0
  180. package/src/components/DataImportModal.tsx +582 -0
  181. package/src/components/DataProfiler.tsx +335 -0
  182. package/src/components/DatabaseDocs.tsx +251 -0
  183. package/src/components/MaskingSettings.tsx +414 -0
  184. package/src/components/MobileNav.tsx +50 -0
  185. package/src/components/NL2SQLPanel.tsx +281 -0
  186. package/src/components/PivotTable.tsx +257 -0
  187. package/src/components/QueryEditor.tsx +760 -0
  188. package/src/components/QueryHistory.tsx +344 -0
  189. package/src/components/QuerySafetyDialog.tsx +290 -0
  190. package/src/components/ResultsGrid.tsx +644 -0
  191. package/src/components/SaveQueryModal.tsx +104 -0
  192. package/src/components/SavedQueries.tsx +128 -0
  193. package/src/components/SchemaDiagram.tsx +473 -0
  194. package/src/components/SchemaDiff.tsx +473 -0
  195. package/src/components/SnapshotTimeline.tsx +116 -0
  196. package/src/components/Studio.tsx +639 -0
  197. package/src/components/TestDataGenerator.tsx +261 -0
  198. package/src/components/VisualExplain.tsx +820 -0
  199. package/src/components/admin/AdminDashboard.tsx +163 -0
  200. package/src/components/admin/tabs/AuditTab.tsx +531 -0
  201. package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
  202. package/src/components/admin/tabs/OperationsTab.tsx +646 -0
  203. package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
  204. package/src/components/admin/tabs/SecurityTab.tsx +284 -0
  205. package/src/components/community-section.tsx +92 -0
  206. package/src/components/icons/db-icons.tsx +84 -0
  207. package/src/components/libredb-logo.tsx +61 -0
  208. package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
  209. package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
  210. package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
  211. package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
  212. package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
  213. package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
  214. package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
  215. package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
  216. package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
  217. package/src/components/results-grid/ResultCard.tsx +111 -0
  218. package/src/components/results-grid/RowDetailSheet.tsx +178 -0
  219. package/src/components/results-grid/StatsBar.tsx +201 -0
  220. package/src/components/results-grid/index.ts +1 -0
  221. package/src/components/results-grid/utils.ts +23 -0
  222. package/src/components/schema-explorer/ColumnList.tsx +53 -0
  223. package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
  224. package/src/components/schema-explorer/TableItem.tsx +210 -0
  225. package/src/components/schema-explorer/index.ts +1 -0
  226. package/src/components/sidebar/ConnectionItem.tsx +105 -0
  227. package/src/components/sidebar/ConnectionsList.tsx +62 -0
  228. package/src/components/sidebar/Sidebar.tsx +130 -0
  229. package/src/components/sidebar/index.ts +2 -0
  230. package/src/components/studio/BottomPanel.tsx +286 -0
  231. package/src/components/studio/QueryToolbar.tsx +180 -0
  232. package/src/components/studio/StudioDesktopHeader.tsx +114 -0
  233. package/src/components/studio/StudioMobileHeader.tsx +340 -0
  234. package/src/components/studio/StudioTabBar.tsx +82 -0
  235. package/src/components/studio/index.ts +5 -0
  236. package/src/components/ui/accordion.tsx +66 -0
  237. package/src/components/ui/alert-dialog.tsx +157 -0
  238. package/src/components/ui/alert.tsx +66 -0
  239. package/src/components/ui/aspect-ratio.tsx +11 -0
  240. package/src/components/ui/avatar.tsx +53 -0
  241. package/src/components/ui/badge.tsx +46 -0
  242. package/src/components/ui/breadcrumb.tsx +109 -0
  243. package/src/components/ui/button-group.tsx +83 -0
  244. package/src/components/ui/button.tsx +60 -0
  245. package/src/components/ui/calendar.tsx +216 -0
  246. package/src/components/ui/card.tsx +92 -0
  247. package/src/components/ui/carousel.tsx +241 -0
  248. package/src/components/ui/chart.tsx +357 -0
  249. package/src/components/ui/checkbox.tsx +32 -0
  250. package/src/components/ui/collapsible.tsx +33 -0
  251. package/src/components/ui/command.tsx +184 -0
  252. package/src/components/ui/context-menu.tsx +252 -0
  253. package/src/components/ui/dialog.tsx +143 -0
  254. package/src/components/ui/drawer.tsx +135 -0
  255. package/src/components/ui/dropdown-menu.tsx +257 -0
  256. package/src/components/ui/empty.tsx +104 -0
  257. package/src/components/ui/field.tsx +248 -0
  258. package/src/components/ui/form.tsx +167 -0
  259. package/src/components/ui/hover-card.tsx +44 -0
  260. package/src/components/ui/input-group.tsx +170 -0
  261. package/src/components/ui/input-otp.tsx +77 -0
  262. package/src/components/ui/input.tsx +21 -0
  263. package/src/components/ui/item.tsx +193 -0
  264. package/src/components/ui/kbd.tsx +28 -0
  265. package/src/components/ui/label.tsx +24 -0
  266. package/src/components/ui/menubar.tsx +276 -0
  267. package/src/components/ui/navigation-menu.tsx +168 -0
  268. package/src/components/ui/pagination.tsx +127 -0
  269. package/src/components/ui/popover.tsx +48 -0
  270. package/src/components/ui/progress.tsx +31 -0
  271. package/src/components/ui/radio-group.tsx +45 -0
  272. package/src/components/ui/resizable.tsx +56 -0
  273. package/src/components/ui/scroll-area.tsx +58 -0
  274. package/src/components/ui/select.tsx +187 -0
  275. package/src/components/ui/separator.tsx +28 -0
  276. package/src/components/ui/sheet.tsx +139 -0
  277. package/src/components/ui/sidebar.tsx +726 -0
  278. package/src/components/ui/skeleton.tsx +13 -0
  279. package/src/components/ui/slider.tsx +63 -0
  280. package/src/components/ui/sonner.tsx +40 -0
  281. package/src/components/ui/spinner.tsx +16 -0
  282. package/src/components/ui/switch.tsx +31 -0
  283. package/src/components/ui/table.tsx +116 -0
  284. package/src/components/ui/tabs.tsx +66 -0
  285. package/src/components/ui/textarea.tsx +18 -0
  286. package/src/components/ui/toggle-group.tsx +83 -0
  287. package/src/components/ui/toggle.tsx +47 -0
  288. package/src/components/ui/tooltip.tsx +61 -0
  289. package/src/exports/components.ts +15 -0
  290. package/src/exports/index.ts +4 -0
  291. package/src/exports/providers.ts +4 -0
  292. package/src/exports/types.ts +26 -0
  293. package/src/hooks/use-ai-chat.ts +182 -0
  294. package/src/hooks/use-all-connections.ts +66 -0
  295. package/src/hooks/use-api-call.ts +71 -0
  296. package/src/hooks/use-auth.ts +51 -0
  297. package/src/hooks/use-connection-form.ts +349 -0
  298. package/src/hooks/use-connection-manager.ts +169 -0
  299. package/src/hooks/use-connection-payload.ts +15 -0
  300. package/src/hooks/use-inline-editing.ts +109 -0
  301. package/src/hooks/use-mobile.ts +20 -0
  302. package/src/hooks/use-monitoring-data.ts +270 -0
  303. package/src/hooks/use-provider-metadata.ts +62 -0
  304. package/src/hooks/use-query-execution.ts +478 -0
  305. package/src/hooks/use-storage-sync.ts +259 -0
  306. package/src/hooks/use-tab-manager.ts +231 -0
  307. package/src/hooks/use-toast.ts +20 -0
  308. package/src/hooks/use-transaction-control.ts +64 -0
  309. package/src/lib/api/error-codes.ts +30 -0
  310. package/src/lib/api/errors.ts +236 -0
  311. package/src/lib/api/with-error-handler.ts +41 -0
  312. package/src/lib/audit.ts +105 -0
  313. package/src/lib/auth.ts +87 -0
  314. package/src/lib/connection-string-parser.ts +172 -0
  315. package/src/lib/data-masking.ts +385 -0
  316. package/src/lib/db/base-provider.ts +325 -0
  317. package/src/lib/db/errors.ts +317 -0
  318. package/src/lib/db/factory.ts +324 -0
  319. package/src/lib/db/index.ts +123 -0
  320. package/src/lib/db/providers/document/index.ts +6 -0
  321. package/src/lib/db/providers/document/mongodb.ts +992 -0
  322. package/src/lib/db/providers/keyvalue/redis.ts +554 -0
  323. package/src/lib/db/providers/sql/index.ts +11 -0
  324. package/src/lib/db/providers/sql/mssql.ts +1065 -0
  325. package/src/lib/db/providers/sql/mysql.ts +978 -0
  326. package/src/lib/db/providers/sql/oracle.ts +1044 -0
  327. package/src/lib/db/providers/sql/postgres.ts +1179 -0
  328. package/src/lib/db/providers/sql/sql-base.ts +174 -0
  329. package/src/lib/db/providers/sql/sqlite.ts +721 -0
  330. package/src/lib/db/types.ts +437 -0
  331. package/src/lib/db/utils/pool-manager.ts +287 -0
  332. package/src/lib/db/utils/query-limiter.ts +239 -0
  333. package/src/lib/db-ui-config.ts +86 -0
  334. package/src/lib/editor/mongodb-completions.ts +172 -0
  335. package/src/lib/editor/sql-completions.ts +280 -0
  336. package/src/lib/llm/base-provider.ts +117 -0
  337. package/src/lib/llm/factory.ts +102 -0
  338. package/src/lib/llm/index.ts +90 -0
  339. package/src/lib/llm/providers/custom.ts +181 -0
  340. package/src/lib/llm/providers/gemini.ts +126 -0
  341. package/src/lib/llm/providers/ollama.ts +154 -0
  342. package/src/lib/llm/providers/openai.ts +146 -0
  343. package/src/lib/llm/types.ts +173 -0
  344. package/src/lib/llm/utils/config.ts +187 -0
  345. package/src/lib/llm/utils/retry.ts +119 -0
  346. package/src/lib/llm/utils/streaming.ts +202 -0
  347. package/src/lib/logger.ts +127 -0
  348. package/src/lib/monitoring-thresholds.ts +44 -0
  349. package/src/lib/oidc.ts +262 -0
  350. package/src/lib/query-generators.ts +61 -0
  351. package/src/lib/schema-diff/diff-engine.ts +273 -0
  352. package/src/lib/schema-diff/migration-generator.ts +208 -0
  353. package/src/lib/schema-diff/types.ts +55 -0
  354. package/src/lib/seed/config-loader.ts +79 -0
  355. package/src/lib/seed/connection-filter.ts +49 -0
  356. package/src/lib/seed/credential-resolver.ts +62 -0
  357. package/src/lib/seed/index.ts +40 -0
  358. package/src/lib/seed/resolve-connection.ts +57 -0
  359. package/src/lib/seed/types.ts +69 -0
  360. package/src/lib/sql/alias-extractor.ts +267 -0
  361. package/src/lib/sql/index.ts +8 -0
  362. package/src/lib/sql/statement-splitter.ts +167 -0
  363. package/src/lib/sql/types.ts +40 -0
  364. package/src/lib/ssh/tunnel.ts +142 -0
  365. package/src/lib/storage/factory.ts +84 -0
  366. package/src/lib/storage/index.ts +14 -0
  367. package/src/lib/storage/local-storage.ts +99 -0
  368. package/src/lib/storage/providers/postgres.ts +225 -0
  369. package/src/lib/storage/providers/sqlite.ts +153 -0
  370. package/src/lib/storage/storage-facade.ts +272 -0
  371. package/src/lib/storage/types.ts +75 -0
  372. package/src/lib/time-series-buffer.ts +58 -0
  373. package/src/lib/types.ts +173 -0
  374. package/src/lib/utils.ts +6 -0
  375. package/src/proxy.ts +104 -0
  376. package/src/types/db-drivers.d.ts +23 -0
  377. package/src/types/html2canvas.d.ts +9 -0
  378. package/tests/api/admin/audit.test.ts +178 -0
  379. package/tests/api/admin/fleet-health.test.ts +183 -0
  380. package/tests/api/ai/autopilot.test.ts +174 -0
  381. package/tests/api/ai/chat.test.ts +250 -0
  382. package/tests/api/ai/describe-schema.test.ts +266 -0
  383. package/tests/api/ai/explain.test.ts +199 -0
  384. package/tests/api/ai/impact.test.ts +168 -0
  385. package/tests/api/ai/index-advisor.test.ts +171 -0
  386. package/tests/api/ai/nl2sql.test.ts +202 -0
  387. package/tests/api/ai/query-safety.test.ts +196 -0
  388. package/tests/api/auth/login.test.ts +170 -0
  389. package/tests/api/auth/logout.test.ts +140 -0
  390. package/tests/api/auth/me.test.ts +73 -0
  391. package/tests/api/auth/oidc-callback.test.ts +215 -0
  392. package/tests/api/auth/oidc-login.test.ts +127 -0
  393. package/tests/api/db/cancel.test.ts +198 -0
  394. package/tests/api/db/disconnect.test.ts +124 -0
  395. package/tests/api/db/health.test.ts +222 -0
  396. package/tests/api/db/maintenance.test.ts +263 -0
  397. package/tests/api/db/monitoring.test.ts +221 -0
  398. package/tests/api/db/multi-query.test.ts +316 -0
  399. package/tests/api/db/pool-stats.test.ts +135 -0
  400. package/tests/api/db/profile.test.ts +330 -0
  401. package/tests/api/db/provider-meta.test.ts +193 -0
  402. package/tests/api/db/query.test.ts +314 -0
  403. package/tests/api/db/schema-snapshot.test.ts +170 -0
  404. package/tests/api/db/schema.test.ts +191 -0
  405. package/tests/api/db/test-connection.test.ts +185 -0
  406. package/tests/api/db/transaction.test.ts +314 -0
  407. package/tests/api/proxy.test.ts +191 -0
  408. package/tests/api/seed/managed-route.test.ts +113 -0
  409. package/tests/api/storage/config.test.ts +42 -0
  410. package/tests/api/storage/storage-routes.test.ts +309 -0
  411. package/tests/components/AIAutopilotPanel.test.tsx +756 -0
  412. package/tests/components/AdminPage.test.tsx +33 -0
  413. package/tests/components/CodeGenerator.test.tsx +182 -0
  414. package/tests/components/CommandPalette.test.tsx +428 -0
  415. package/tests/components/CommunitySection.test.tsx +91 -0
  416. package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
  417. package/tests/components/ConnectionModal.test.tsx +570 -0
  418. package/tests/components/CreateTableModal.test.tsx +383 -0
  419. package/tests/components/DataCharts.test.tsx +739 -0
  420. package/tests/components/DataImportModal.test.tsx +751 -0
  421. package/tests/components/DataProfiler.test.tsx +589 -0
  422. package/tests/components/DatabaseDocs.test.tsx +353 -0
  423. package/tests/components/LoginPage.test.tsx +163 -0
  424. package/tests/components/LoginPageOIDC.test.tsx +92 -0
  425. package/tests/components/MaskingSettings.test.tsx +498 -0
  426. package/tests/components/MobileNav.test.tsx +30 -0
  427. package/tests/components/MonitoringPage.test.tsx +32 -0
  428. package/tests/components/NL2SQLPanel.test.tsx +621 -0
  429. package/tests/components/Page.test.tsx +33 -0
  430. package/tests/components/PivotTable.test.tsx +350 -0
  431. package/tests/components/QueryEditor.test.tsx +1730 -0
  432. package/tests/components/QueryHistory.test.tsx +572 -0
  433. package/tests/components/QuerySafetyDialog.test.tsx +586 -0
  434. package/tests/components/ResultsGrid.test.tsx +804 -0
  435. package/tests/components/RootLayout.test.tsx +83 -0
  436. package/tests/components/SaveQueryModal.test.tsx +25 -0
  437. package/tests/components/SavedQueries.test.tsx +43 -0
  438. package/tests/components/SchemaDiagram.test.tsx +1034 -0
  439. package/tests/components/SchemaDiff.test.tsx +906 -0
  440. package/tests/components/SnapshotTimeline.test.tsx +174 -0
  441. package/tests/components/Studio.test.tsx +1030 -0
  442. package/tests/components/TestDataGenerator.test.tsx +291 -0
  443. package/tests/components/VisualExplain.test.tsx +704 -0
  444. package/tests/components/admin/AdminDashboard.test.tsx +205 -0
  445. package/tests/components/admin/AuditTab.test.tsx +220 -0
  446. package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
  447. package/tests/components/admin/OperationsTab.test.tsx +975 -0
  448. package/tests/components/admin/OverviewTab.test.tsx +254 -0
  449. package/tests/components/admin/SecurityTab.test.tsx +467 -0
  450. package/tests/components/monitoring/MetricChart.test.tsx +111 -0
  451. package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
  452. package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
  453. package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
  454. package/tests/components/monitoring/PoolTab.test.tsx +42 -0
  455. package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
  456. package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
  457. package/tests/components/monitoring/StorageTab.test.tsx +127 -0
  458. package/tests/components/monitoring/TablesTab.test.tsx +153 -0
  459. package/tests/components/results-grid/ResultCard.test.tsx +105 -0
  460. package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
  461. package/tests/components/results-grid/StatsBar.test.tsx +162 -0
  462. package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
  463. package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
  464. package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
  465. package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
  466. package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
  467. package/tests/components/sidebar/Sidebar.test.tsx +187 -0
  468. package/tests/components/studio/BottomPanel.test.tsx +383 -0
  469. package/tests/components/studio/QueryToolbar.test.tsx +321 -0
  470. package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
  471. package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
  472. package/tests/components/studio/StudioTabBar.test.tsx +331 -0
  473. package/tests/fixtures/connections.ts +96 -0
  474. package/tests/fixtures/masking-configs.ts +86 -0
  475. package/tests/fixtures/query-results.ts +71 -0
  476. package/tests/fixtures/schemas.ts +64 -0
  477. package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
  478. package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
  479. package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
  480. package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
  481. package/tests/fixtures/seed-connections/valid-config.json +15 -0
  482. package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
  483. package/tests/helpers/mock-fetch.ts +59 -0
  484. package/tests/helpers/mock-monaco.ts +112 -0
  485. package/tests/helpers/mock-navigation.ts +28 -0
  486. package/tests/helpers/mock-next.ts +80 -0
  487. package/tests/helpers/mock-provider.ts +133 -0
  488. package/tests/helpers/mock-sonner.ts +29 -0
  489. package/tests/helpers/render-with-providers.tsx +19 -0
  490. package/tests/hooks/use-ai-chat.test.ts +600 -0
  491. package/tests/hooks/use-auth.test.ts +371 -0
  492. package/tests/hooks/use-connection-form.test.ts +743 -0
  493. package/tests/hooks/use-connection-manager.test.ts +466 -0
  494. package/tests/hooks/use-inline-editing.test.ts +321 -0
  495. package/tests/hooks/use-mobile.test.ts +177 -0
  496. package/tests/hooks/use-monitoring-data.test.ts +819 -0
  497. package/tests/hooks/use-provider-metadata.test.ts +228 -0
  498. package/tests/hooks/use-query-execution.test.ts +1212 -0
  499. package/tests/hooks/use-tab-manager.test.ts +756 -0
  500. package/tests/hooks/use-toast.test.ts +74 -0
  501. package/tests/hooks/use-transaction-control.test.ts +211 -0
  502. package/tests/integration/db/mongodb-provider.test.ts +698 -0
  503. package/tests/integration/db/mssql-provider.test.ts +840 -0
  504. package/tests/integration/db/mysql-provider.test.ts +872 -0
  505. package/tests/integration/db/oracle-provider.test.ts +843 -0
  506. package/tests/integration/db/postgres-provider.test.ts +1382 -0
  507. package/tests/integration/db/redis-provider.test.ts +526 -0
  508. package/tests/integration/db/sqlite-provider.test.ts +480 -0
  509. package/tests/integration/seed/seed-pipeline.test.ts +102 -0
  510. package/tests/isolated/factory-singleton.test.ts +150 -0
  511. package/tests/isolated/use-storage-sync.test.ts +389 -0
  512. package/tests/run-components.sh +196 -0
  513. package/tests/setup-dom.ts +58 -0
  514. package/tests/setup.ts +40 -0
  515. package/tests/unit/api-errors.test.ts +210 -0
  516. package/tests/unit/code-generator-functions.test.ts +271 -0
  517. package/tests/unit/components/column-list.test.tsx +190 -0
  518. package/tests/unit/components/data-import-modal.test.tsx +441 -0
  519. package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
  520. package/tests/unit/data-charts-functions.test.ts +496 -0
  521. package/tests/unit/data-import-functions.test.ts +320 -0
  522. package/tests/unit/data-import-utils.test.ts +125 -0
  523. package/tests/unit/db/base-provider.test.ts +517 -0
  524. package/tests/unit/db/errors.test.ts +403 -0
  525. package/tests/unit/db/factory.test.ts +436 -0
  526. package/tests/unit/db/pool-manager.test.ts +440 -0
  527. package/tests/unit/db/query-limiter.test.ts +387 -0
  528. package/tests/unit/db/sql-base.test.ts +438 -0
  529. package/tests/unit/lib/api/error-codes.test.ts +39 -0
  530. package/tests/unit/lib/audit.test.ts +326 -0
  531. package/tests/unit/lib/auth.test.ts +146 -0
  532. package/tests/unit/lib/connection-string-parser.test.ts +424 -0
  533. package/tests/unit/lib/data-masking.test.ts +583 -0
  534. package/tests/unit/lib/db-icons.test.tsx +41 -0
  535. package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
  536. package/tests/unit/lib/oidc.test.ts +509 -0
  537. package/tests/unit/lib/query-generators.test.ts +127 -0
  538. package/tests/unit/lib/storage/factory.test.ts +71 -0
  539. package/tests/unit/lib/storage/local-storage.test.ts +114 -0
  540. package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
  541. package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
  542. package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
  543. package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
  544. package/tests/unit/lib/storage.test.ts +317 -0
  545. package/tests/unit/lib/time-series-buffer.test.ts +212 -0
  546. package/tests/unit/lib/utils.test.ts +24 -0
  547. package/tests/unit/llm/base-provider.test.ts +238 -0
  548. package/tests/unit/llm/config.test.ts +262 -0
  549. package/tests/unit/llm/custom-provider.test.ts +281 -0
  550. package/tests/unit/llm/gemini-provider.test.ts +248 -0
  551. package/tests/unit/llm/llm-factory.test.ts +155 -0
  552. package/tests/unit/llm/ollama-provider.test.ts +288 -0
  553. package/tests/unit/llm/openai-provider.test.ts +324 -0
  554. package/tests/unit/llm/retry.test.ts +180 -0
  555. package/tests/unit/llm/streaming.test.ts +355 -0
  556. package/tests/unit/logger.test.ts +198 -0
  557. package/tests/unit/mongodb-completions.test.ts +516 -0
  558. package/tests/unit/pivot-table-functions.test.ts +76 -0
  559. package/tests/unit/query-cancelled-error.test.ts +81 -0
  560. package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
  561. package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
  562. package/tests/unit/seed/config-loader.test.ts +73 -0
  563. package/tests/unit/seed/connection-filter.test.ts +91 -0
  564. package/tests/unit/seed/credential-resolver.test.ts +85 -0
  565. package/tests/unit/seed/index.test.ts +72 -0
  566. package/tests/unit/seed/resolve-connection.test.ts +74 -0
  567. package/tests/unit/seed/types.test.ts +129 -0
  568. package/tests/unit/sql/alias-extractor.test.ts +444 -0
  569. package/tests/unit/sql/statement-splitter.test.ts +348 -0
  570. package/tests/unit/sql-completions.test.ts +463 -0
  571. package/tests/unit/ssh-tunnel.test.ts +465 -0
  572. package/tsconfig.json +42 -0
@@ -0,0 +1,586 @@
1
+ import '../setup-dom';
2
+ import '../helpers/mock-sonner';
3
+ import '../helpers/mock-navigation';
4
+
5
+ import React from 'react';
6
+ import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
7
+ import { cleanup, fireEvent, render, waitFor } from '@testing-library/react';
8
+ import { QuerySafetyDialog, isDangerousQuery } from '@/components/QuerySafetyDialog';
9
+
10
+ function createStreamResponse({
11
+ chunks,
12
+ ok = true,
13
+ status = 200,
14
+ jsonBody = {},
15
+ }: {
16
+ chunks: string[];
17
+ ok?: boolean;
18
+ status?: number;
19
+ jsonBody?: unknown;
20
+ }) {
21
+ let idx = 0;
22
+ return {
23
+ ok,
24
+ status,
25
+ body: {
26
+ getReader: () => ({
27
+ read: async () => {
28
+ if (idx >= chunks.length) {
29
+ return { done: true, value: undefined };
30
+ }
31
+ const value = new TextEncoder().encode(chunks[idx]);
32
+ idx += 1;
33
+ return { done: false, value };
34
+ },
35
+ }),
36
+ },
37
+ json: async () => jsonBody,
38
+ } as unknown as Response;
39
+ }
40
+
41
+ describe('QuerySafetyDialog', () => {
42
+ const onClose = mock(() => {});
43
+ const onProceed = mock(() => {});
44
+
45
+ beforeEach(() => {
46
+ onClose.mockClear();
47
+ onProceed.mockClear();
48
+ });
49
+
50
+ afterEach(() => {
51
+ cleanup();
52
+ });
53
+
54
+ test('renders nothing when dialog is closed', () => {
55
+ const { container } = render(
56
+ <QuerySafetyDialog
57
+ isOpen={false}
58
+ query="SELECT 1"
59
+ schemaContext=""
60
+ onClose={onClose}
61
+ onProceed={onProceed}
62
+ />
63
+ );
64
+ expect(container.textContent).toBe('');
65
+ });
66
+
67
+ test('renders parsed high-risk analysis and caution action label', async () => {
68
+ const payload = {
69
+ riskLevel: 'high',
70
+ summary: 'This query can update many rows.',
71
+ warnings: [{ type: 'update', severity: 'warning', message: 'Potential full-table update', detail: 'WHERE clause is too broad.' }],
72
+ affectedRows: '12000',
73
+ cascadeEffects: 'none',
74
+ recommendation: 'Add stricter predicates before execution.',
75
+ };
76
+ const markdownJson = `\`\`\`json\n${JSON.stringify(payload)}\n\`\`\``;
77
+ globalThis.fetch = mock(async () => createStreamResponse({ chunks: [markdownJson] })) as unknown as typeof fetch;
78
+
79
+ const { queryByText } = render(
80
+ <QuerySafetyDialog
81
+ isOpen
82
+ query="UPDATE users SET role = 'admin'"
83
+ schemaContext='[{"name":"users","rowCount":12000,"columns":[{"name":"id","type":"integer"}]}]'
84
+ databaseType="postgres"
85
+ onClose={onClose}
86
+ onProceed={onProceed}
87
+ />
88
+ );
89
+
90
+ await waitFor(() => {
91
+ expect(queryByText('High Risk')).not.toBeNull();
92
+ expect(queryByText('This query can update many rows.')).not.toBeNull();
93
+ expect(queryByText('Potential full-table update')).not.toBeNull();
94
+ expect(queryByText('Affected rows:')).not.toBeNull();
95
+ expect(queryByText('Proceed with Caution')).not.toBeNull();
96
+ });
97
+ });
98
+
99
+ test('shows raw response when analysis payload cannot be parsed', async () => {
100
+ globalThis.fetch = mock(async () => createStreamResponse({ chunks: ['Plain text analysis output'] })) as unknown as typeof fetch;
101
+
102
+ const { queryByText } = render(
103
+ <QuerySafetyDialog
104
+ isOpen
105
+ query="DELETE FROM users WHERE id = 5"
106
+ schemaContext=""
107
+ onClose={onClose}
108
+ onProceed={onProceed}
109
+ />
110
+ );
111
+
112
+ await waitFor(() => {
113
+ expect(queryByText('Plain text analysis output')).not.toBeNull();
114
+ });
115
+ });
116
+
117
+ test('shows API error message when backend returns non-ok response', async () => {
118
+ globalThis.fetch = mock(async () =>
119
+ createStreamResponse({
120
+ chunks: [],
121
+ ok: false,
122
+ status: 400,
123
+ jsonBody: { error: 'Rate limit exceeded' },
124
+ })
125
+ ) as unknown as typeof fetch;
126
+
127
+ const { queryByText } = render(
128
+ <QuerySafetyDialog
129
+ isOpen
130
+ query="DROP TABLE users"
131
+ schemaContext=""
132
+ onClose={onClose}
133
+ onProceed={onProceed}
134
+ />
135
+ );
136
+
137
+ await waitFor(() => {
138
+ expect(queryByText('Rate limit exceeded')).not.toBeNull();
139
+ });
140
+ });
141
+
142
+ test('calls onClose and onProceed from action buttons', async () => {
143
+ const safePayload = {
144
+ riskLevel: 'safe',
145
+ summary: 'Query looks safe.',
146
+ warnings: [],
147
+ affectedRows: 'none',
148
+ cascadeEffects: 'none',
149
+ recommendation: 'Proceed.',
150
+ };
151
+ globalThis.fetch = mock(async () =>
152
+ createStreamResponse({ chunks: [JSON.stringify(safePayload)] })
153
+ ) as unknown as typeof fetch;
154
+
155
+ const { queryByText, container } = render(
156
+ <QuerySafetyDialog
157
+ isOpen
158
+ query="SELECT * FROM users"
159
+ schemaContext=""
160
+ onClose={onClose}
161
+ onProceed={onProceed}
162
+ />
163
+ );
164
+
165
+ await waitFor(() => {
166
+ expect(queryByText('Execute Query')).not.toBeNull();
167
+ });
168
+
169
+ const cancelButton = queryByText('Cancel');
170
+ expect(cancelButton).not.toBeNull();
171
+ fireEvent.click(cancelButton!);
172
+ expect(onClose).toHaveBeenCalled();
173
+
174
+ const proceedButton = queryByText('Execute Query');
175
+ expect(proceedButton).not.toBeNull();
176
+ fireEvent.click(proceedButton!);
177
+ expect(onProceed).toHaveBeenCalled();
178
+
179
+ const closeIconButton = container.querySelector('button');
180
+ expect(closeIconButton).not.toBeNull();
181
+ fireEvent.click(closeIconButton!);
182
+ expect(onClose.mock.calls.length).toBeGreaterThan(1);
183
+ });
184
+
185
+ test('truncates query preview at 300 characters with ellipsis', async () => {
186
+ const longQuery = 'SELECT ' + 'a'.repeat(350) + ' FROM users';
187
+ const safePayload = {
188
+ riskLevel: 'safe',
189
+ summary: 'Safe query.',
190
+ warnings: [],
191
+ affectedRows: 'none',
192
+ cascadeEffects: 'none',
193
+ recommendation: 'OK.',
194
+ };
195
+ globalThis.fetch = mock(async () =>
196
+ createStreamResponse({ chunks: [JSON.stringify(safePayload)] })
197
+ ) as unknown as typeof fetch;
198
+
199
+ const { container } = render(
200
+ <QuerySafetyDialog
201
+ isOpen
202
+ query={longQuery}
203
+ schemaContext=""
204
+ onClose={onClose}
205
+ onProceed={onProceed}
206
+ />
207
+ );
208
+
209
+ const preElement = container.querySelector('pre');
210
+ expect(preElement).not.toBeNull();
211
+ const preText = preElement!.textContent || '';
212
+ expect(preText.length).toBeLessThanOrEqual(303); // 300 chars + '...'
213
+ expect(preText.endsWith('...')).toBe(true);
214
+ expect(preText).toBe(longQuery.substring(0, 300) + '...');
215
+ });
216
+
217
+ test('shows "Execute Anyway" button text for critical risk', async () => {
218
+ const criticalPayload = {
219
+ riskLevel: 'critical',
220
+ summary: 'Extremely dangerous operation.',
221
+ warnings: [],
222
+ affectedRows: 'all',
223
+ cascadeEffects: 'none',
224
+ recommendation: 'Do not execute.',
225
+ };
226
+ globalThis.fetch = mock(async () =>
227
+ createStreamResponse({ chunks: [`\`\`\`json\n${JSON.stringify(criticalPayload)}\n\`\`\``] })
228
+ ) as unknown as typeof fetch;
229
+
230
+ const { queryByText } = render(
231
+ <QuerySafetyDialog
232
+ isOpen
233
+ query="DROP DATABASE production"
234
+ schemaContext=""
235
+ onClose={onClose}
236
+ onProceed={onProceed}
237
+ />
238
+ );
239
+
240
+ await waitFor(() => {
241
+ expect(queryByText('Execute Anyway')).not.toBeNull();
242
+ expect(queryByText('Critical Risk')).not.toBeNull();
243
+ });
244
+ });
245
+
246
+ test('shows "Proceed with Caution" for high risk', async () => {
247
+ const highPayload = {
248
+ riskLevel: 'high',
249
+ summary: 'High risk detected.',
250
+ warnings: [],
251
+ affectedRows: 'none',
252
+ cascadeEffects: 'none',
253
+ recommendation: 'Be careful.',
254
+ };
255
+ globalThis.fetch = mock(async () =>
256
+ createStreamResponse({ chunks: [JSON.stringify(highPayload)] })
257
+ ) as unknown as typeof fetch;
258
+
259
+ const { queryByText } = render(
260
+ <QuerySafetyDialog
261
+ isOpen
262
+ query="DELETE FROM orders"
263
+ schemaContext=""
264
+ onClose={onClose}
265
+ onProceed={onProceed}
266
+ />
267
+ );
268
+
269
+ await waitFor(() => {
270
+ expect(queryByText('Proceed with Caution')).not.toBeNull();
271
+ });
272
+ });
273
+
274
+ test('shows "Execute Query" for safe, low, and medium risk levels', async () => {
275
+ for (const riskLevel of ['safe', 'low', 'medium'] as const) {
276
+ cleanup();
277
+ onClose.mockClear();
278
+ onProceed.mockClear();
279
+
280
+ const payload = {
281
+ riskLevel,
282
+ summary: `${riskLevel} level query.`,
283
+ warnings: [],
284
+ affectedRows: 'none',
285
+ cascadeEffects: 'none',
286
+ recommendation: 'OK.',
287
+ };
288
+ globalThis.fetch = mock(async () =>
289
+ createStreamResponse({ chunks: [JSON.stringify(payload)] })
290
+ ) as unknown as typeof fetch;
291
+
292
+ const { queryByText } = render(
293
+ <QuerySafetyDialog
294
+ isOpen
295
+ query="SELECT 1"
296
+ schemaContext=""
297
+ onClose={onClose}
298
+ onProceed={onProceed}
299
+ />
300
+ );
301
+
302
+ await waitFor(() => {
303
+ expect(queryByText('Execute Query')).not.toBeNull();
304
+ });
305
+
306
+ cleanup();
307
+ }
308
+ });
309
+
310
+ test('displays cascadeEffects when not "none"', async () => {
311
+ const payload = {
312
+ riskLevel: 'high',
313
+ summary: 'Cascade risk.',
314
+ warnings: [],
315
+ affectedRows: 'none',
316
+ cascadeEffects: 'Will delete related rows in orders and invoices tables',
317
+ recommendation: 'Check FK constraints.',
318
+ };
319
+ globalThis.fetch = mock(async () =>
320
+ createStreamResponse({ chunks: [JSON.stringify(payload)] })
321
+ ) as unknown as typeof fetch;
322
+
323
+ const { queryByText } = render(
324
+ <QuerySafetyDialog
325
+ isOpen
326
+ query="DELETE FROM customers WHERE id = 1"
327
+ schemaContext=""
328
+ onClose={onClose}
329
+ onProceed={onProceed}
330
+ />
331
+ );
332
+
333
+ await waitFor(() => {
334
+ expect(queryByText('Cascade effects:')).not.toBeNull();
335
+ expect(queryByText('Will delete related rows in orders and invoices tables')).not.toBeNull();
336
+ });
337
+ });
338
+
339
+ test('displays affectedRows when not "none"', async () => {
340
+ const payload = {
341
+ riskLevel: 'medium',
342
+ summary: 'Medium risk update.',
343
+ warnings: [],
344
+ affectedRows: '5000',
345
+ cascadeEffects: 'none',
346
+ recommendation: 'Double check.',
347
+ };
348
+ globalThis.fetch = mock(async () =>
349
+ createStreamResponse({ chunks: [JSON.stringify(payload)] })
350
+ ) as unknown as typeof fetch;
351
+
352
+ const { queryByText } = render(
353
+ <QuerySafetyDialog
354
+ isOpen
355
+ query="UPDATE users SET status = 'inactive'"
356
+ schemaContext=""
357
+ onClose={onClose}
358
+ onProceed={onProceed}
359
+ />
360
+ );
361
+
362
+ await waitFor(() => {
363
+ expect(queryByText('Affected rows:')).not.toBeNull();
364
+ expect(queryByText('5000')).not.toBeNull();
365
+ });
366
+ });
367
+
368
+ test('applies correct severity styling to warnings (critical=red, warning=amber, info=blue)', async () => {
369
+ const payload = {
370
+ riskLevel: 'high',
371
+ summary: 'Multiple warnings.',
372
+ warnings: [
373
+ { type: 'drop', severity: 'critical', message: 'Critical warning', detail: 'Critical detail' },
374
+ { type: 'update', severity: 'warning', message: 'Warning level', detail: 'Warning detail' },
375
+ { type: 'select', severity: 'info', message: 'Info level', detail: 'Info detail' },
376
+ ],
377
+ affectedRows: 'none',
378
+ cascadeEffects: 'none',
379
+ recommendation: 'Review carefully.',
380
+ };
381
+ globalThis.fetch = mock(async () =>
382
+ createStreamResponse({ chunks: [JSON.stringify(payload)] })
383
+ ) as unknown as typeof fetch;
384
+
385
+ const { queryByText } = render(
386
+ <QuerySafetyDialog
387
+ isOpen
388
+ query="DROP TABLE important_data"
389
+ schemaContext=""
390
+ onClose={onClose}
391
+ onProceed={onProceed}
392
+ />
393
+ );
394
+
395
+ await waitFor(() => {
396
+ expect(queryByText('Critical warning')).not.toBeNull();
397
+ expect(queryByText('Warning level')).not.toBeNull();
398
+ expect(queryByText('Info level')).not.toBeNull();
399
+ });
400
+
401
+ const criticalEl = queryByText('Critical warning')!.closest('div');
402
+ expect(criticalEl?.className).toContain('bg-red-500/5');
403
+ expect(criticalEl?.className).toContain('border-red-500/20');
404
+
405
+ const warningEl = queryByText('Warning level')!.closest('div');
406
+ expect(warningEl?.className).toContain('bg-amber-500/5');
407
+ expect(warningEl?.className).toContain('border-amber-500/20');
408
+
409
+ const infoEl = queryByText('Info level')!.closest('div');
410
+ expect(infoEl?.className).toContain('bg-blue-500/5');
411
+ expect(infoEl?.className).toContain('border-blue-500/20');
412
+ });
413
+
414
+ test('falls back to substring truncation when schemaContext is invalid JSON', async () => {
415
+ const invalidSchema = 'this is not valid JSON but is longer than we need for testing purposes';
416
+ const safePayload = {
417
+ riskLevel: 'safe',
418
+ summary: 'Query is safe.',
419
+ warnings: [],
420
+ affectedRows: 'none',
421
+ cascadeEffects: 'none',
422
+ recommendation: 'Proceed.',
423
+ };
424
+ const fetchMock = mock(async () =>
425
+ createStreamResponse({ chunks: [JSON.stringify(safePayload)] })
426
+ );
427
+ globalThis.fetch = fetchMock as unknown as typeof fetch;
428
+
429
+ const { queryByText } = render(
430
+ <QuerySafetyDialog
431
+ isOpen
432
+ query="SELECT * FROM users"
433
+ schemaContext={invalidSchema}
434
+ databaseType="postgres"
435
+ onClose={onClose}
436
+ onProceed={onProceed}
437
+ />
438
+ );
439
+
440
+ await waitFor(() => {
441
+ expect(queryByText('Query is safe.')).not.toBeNull();
442
+ });
443
+
444
+ // Verify fetch was called with the fallback truncated schema (substring of invalid JSON)
445
+ expect(fetchMock).toHaveBeenCalled();
446
+ const callBody = JSON.parse(((fetchMock.mock.calls as unknown[][])[0][1] as RequestInit).body as string);
447
+ expect(callBody.schemaContext).toBe(invalidSchema.substring(0, 2000));
448
+ });
449
+
450
+ test('parses plain JSON response without code block wrapping', async () => {
451
+ const payload = {
452
+ riskLevel: 'low',
453
+ summary: 'Low risk query detected.',
454
+ warnings: [{ type: 'select', severity: 'info', message: 'Large result set', detail: 'May return many rows.' }],
455
+ affectedRows: 'none',
456
+ cascadeEffects: 'none',
457
+ recommendation: 'Consider adding LIMIT.',
458
+ };
459
+ // Send plain JSON without ```json wrapper
460
+ globalThis.fetch = mock(async () =>
461
+ createStreamResponse({ chunks: [JSON.stringify(payload)] })
462
+ ) as unknown as typeof fetch;
463
+
464
+ const { queryByText } = render(
465
+ <QuerySafetyDialog
466
+ isOpen
467
+ query="SELECT * FROM large_table"
468
+ schemaContext=""
469
+ onClose={onClose}
470
+ onProceed={onProceed}
471
+ />
472
+ );
473
+
474
+ await waitFor(() => {
475
+ expect(queryByText('Low Risk')).not.toBeNull();
476
+ expect(queryByText('Low risk query detected.')).not.toBeNull();
477
+ expect(queryByText('Large result set')).not.toBeNull();
478
+ expect(queryByText('Execute Query')).not.toBeNull();
479
+ });
480
+ });
481
+
482
+ test('displays raw response text when JSON parsing fails completely', async () => {
483
+ const rawText = 'The query appears safe but I cannot provide structured analysis right now.';
484
+ globalThis.fetch = mock(async () =>
485
+ createStreamResponse({ chunks: [rawText] })
486
+ ) as unknown as typeof fetch;
487
+
488
+ const { queryByText } = render(
489
+ <QuerySafetyDialog
490
+ isOpen
491
+ query="DELETE FROM temp_table WHERE created < NOW()"
492
+ schemaContext=""
493
+ onClose={onClose}
494
+ onProceed={onProceed}
495
+ />
496
+ );
497
+
498
+ await waitFor(() => {
499
+ expect(queryByText(rawText)).not.toBeNull();
500
+ });
501
+ });
502
+
503
+ test('high and critical risk buttons have red background class', async () => {
504
+ // Test critical risk button
505
+ const criticalPayload = {
506
+ riskLevel: 'critical',
507
+ summary: 'Critical operation.',
508
+ warnings: [],
509
+ affectedRows: 'all',
510
+ cascadeEffects: 'none',
511
+ recommendation: 'Stop.',
512
+ };
513
+ globalThis.fetch = mock(async () =>
514
+ createStreamResponse({ chunks: [JSON.stringify(criticalPayload)] })
515
+ ) as unknown as typeof fetch;
516
+
517
+ const { queryByText, unmount } = render(
518
+ <QuerySafetyDialog
519
+ isOpen
520
+ query="TRUNCATE TABLE users"
521
+ schemaContext=""
522
+ onClose={onClose}
523
+ onProceed={onProceed}
524
+ />
525
+ );
526
+
527
+ await waitFor(() => {
528
+ expect(queryByText('Execute Anyway')).not.toBeNull();
529
+ });
530
+
531
+ const criticalButton = queryByText('Execute Anyway')!.closest('button');
532
+ expect(criticalButton?.className).toContain('bg-red-600');
533
+
534
+ unmount();
535
+ cleanup();
536
+
537
+ // Test high risk button
538
+ const highPayload = {
539
+ riskLevel: 'high',
540
+ summary: 'High risk operation.',
541
+ warnings: [],
542
+ affectedRows: '10000',
543
+ cascadeEffects: 'none',
544
+ recommendation: 'Be very careful.',
545
+ };
546
+ globalThis.fetch = mock(async () =>
547
+ createStreamResponse({ chunks: [JSON.stringify(highPayload)] })
548
+ ) as unknown as typeof fetch;
549
+
550
+ const result2 = render(
551
+ <QuerySafetyDialog
552
+ isOpen
553
+ query="DELETE FROM audit_log"
554
+ schemaContext=""
555
+ onClose={onClose}
556
+ onProceed={onProceed}
557
+ />
558
+ );
559
+
560
+ await waitFor(() => {
561
+ expect(result2.queryByText('Proceed with Caution')).not.toBeNull();
562
+ });
563
+
564
+ const highButton = result2.queryByText('Proceed with Caution')!.closest('button');
565
+ expect(highButton?.className).toContain('bg-red-600');
566
+ });
567
+ });
568
+
569
+ describe('isDangerousQuery', () => {
570
+ test('detects dangerous DML and DDL statements', () => {
571
+ expect(isDangerousQuery('DELETE FROM users')).toBe(true);
572
+ expect(isDangerousQuery('DROP TABLE users')).toBe(true);
573
+ expect(isDangerousQuery('ALTER TABLE users ADD COLUMN x int')).toBe(true);
574
+ expect(isDangerousQuery('GRANT SELECT ON users TO analyst')).toBe(true);
575
+ });
576
+
577
+ test('detects UPDATE/DELETE without WHERE as dangerous', () => {
578
+ expect(isDangerousQuery('UPDATE users SET active = false')).toBe(true);
579
+ expect(isDangerousQuery('DELETE FROM sessions')).toBe(true);
580
+ });
581
+
582
+ test('allows read-only queries', () => {
583
+ expect(isDangerousQuery('SELECT * FROM users')).toBe(false);
584
+ expect(isDangerousQuery('WITH cte AS (SELECT 1) SELECT * FROM cte')).toBe(false);
585
+ });
586
+ });