@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,383 @@
1
+ import '../setup-dom';
2
+ import { mockToastError } from '../helpers/mock-sonner';
3
+ import '../helpers/mock-navigation';
4
+
5
+ import React from 'react';
6
+ import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
7
+ import { act, cleanup, fireEvent, render, within } from '@testing-library/react';
8
+ import { CreateTableModal } from '@/components/CreateTableModal';
9
+
10
+ describe('CreateTableModal', () => {
11
+ afterEach(() => { cleanup(); });
12
+ beforeEach(() => { mockToastError.mockClear(); });
13
+
14
+ test('renders dialog content when isOpen', () => {
15
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
16
+ const body = within(baseElement);
17
+ expect(body.queryByText('Create New Table')).not.toBeNull();
18
+ expect(body.queryByText('SQL Preview')).not.toBeNull();
19
+ expect(body.queryByText('Add Column')).not.toBeNull();
20
+ expect(body.queryByText('General Settings')).not.toBeNull();
21
+ expect(body.queryByText('Column Definitions')).not.toBeNull();
22
+ });
23
+
24
+ test('shows default id column and SQL placeholder', () => {
25
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
26
+ expect(baseElement.textContent).toContain('-- Name your table to see SQL');
27
+ expect(baseElement.textContent).toContain('SERIAL (Auto-Inc)');
28
+ });
29
+
30
+ // ── 1. Add Column button adds new row ──────────────────────────────────────
31
+
32
+ test('Add Column button adds a new column row', () => {
33
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
34
+ const body = within(baseElement);
35
+
36
+ // Initially there is 1 column (the default "id" column)
37
+ const initialInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
38
+ expect(initialInputs.length).toBe(1);
39
+
40
+ // Click "Add Column"
41
+ const addBtn = body.getByText('Add Column');
42
+ act(() => { fireEvent.click(addBtn); });
43
+
44
+ // Now there should be 2 column rows
45
+ const updatedInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
46
+ expect(updatedInputs.length).toBe(2);
47
+ });
48
+
49
+ // ── 2. Remove column removes a row ─────────────────────────────────────────
50
+
51
+ test('remove button removes a column row', () => {
52
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
53
+ const body = within(baseElement);
54
+
55
+ // Add a second column first
56
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
57
+ expect(baseElement.querySelectorAll('input[placeholder="column_name"]').length).toBe(2);
58
+
59
+ // Find trash/remove buttons — they are the last button in each column row
60
+ const trashButtons = baseElement.querySelectorAll('button');
61
+ const removeButtons = Array.from(trashButtons).filter(btn => {
62
+ const icon = btn.querySelector('[data-icon="Trash2"]') || btn.querySelector('svg');
63
+ return icon !== null && btn.textContent === '';
64
+ });
65
+
66
+ // Click the last remove button (removes the newly added column)
67
+ act(() => { fireEvent.click(removeButtons[removeButtons.length - 1]); });
68
+
69
+ expect(baseElement.querySelectorAll('input[placeholder="column_name"]').length).toBe(1);
70
+ });
71
+
72
+ // ── 3. Cannot remove last remaining column ─────────────────────────────────
73
+
74
+ test('cannot remove the last remaining column', () => {
75
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
76
+
77
+ // Only 1 column (id) exists
78
+ expect(baseElement.querySelectorAll('input[placeholder="column_name"]').length).toBe(1);
79
+
80
+ // Find trash/remove buttons
81
+ const trashButtons = baseElement.querySelectorAll('button');
82
+ const removeButtons = Array.from(trashButtons).filter(btn => {
83
+ const icon = btn.querySelector('[data-icon="Trash2"]') || btn.querySelector('svg');
84
+ return icon !== null && btn.textContent === '';
85
+ });
86
+
87
+ // Click the only remove button
88
+ if (removeButtons.length > 0) {
89
+ act(() => { fireEvent.click(removeButtons[0]); });
90
+ }
91
+
92
+ // Still 1 column — guard prevents removal
93
+ expect(baseElement.querySelectorAll('input[placeholder="column_name"]').length).toBe(1);
94
+ });
95
+
96
+ // ── 4. Table name sanitization (lowercase + underscores) ───────────────────
97
+
98
+ test('table name input sanitizes to lowercase and underscores', () => {
99
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
100
+
101
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
102
+ expect(tableNameInput).not.toBeNull();
103
+
104
+ // Type mixed-case with special characters
105
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'My Table-Name!123' } }); });
106
+
107
+ // The onChange handler lowercases and replaces non-alphanumeric/underscore with _
108
+ expect(tableNameInput.value).toBe('my_table_name_123');
109
+ });
110
+
111
+ // ── 5. SQL preview with column definitions ─────────────────────────────────
112
+
113
+ test('SQL preview shows CREATE TABLE with column definitions', () => {
114
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
115
+
116
+ // Set table name to trigger SQL generation
117
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
118
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'users' } }); });
119
+
120
+ const text = baseElement.textContent || '';
121
+ expect(text).toContain('CREATE TABLE users');
122
+ expect(text).toContain('id SERIAL PRIMARY KEY');
123
+ });
124
+
125
+ // ── 6. NOT NULL in SQL for non-nullable columns ────────────────────────────
126
+
127
+ test('NOT NULL appears in SQL for non-nullable non-PK column', () => {
128
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
129
+ const body = within(baseElement);
130
+
131
+ // Set table name
132
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
133
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'orders' } }); });
134
+
135
+ // Add a new column
136
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
137
+
138
+ // Set column name for the new column (second input)
139
+ const colInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
140
+ act(() => { fireEvent.change(colInputs[1], { target: { value: 'status' } }); });
141
+
142
+ // New column defaults: isNullable=true, isPrimary=false
143
+ // Find Nullable checkboxes (role="checkbox") — Radix Checkbox renders as button[role="checkbox"]
144
+ const checkboxes = baseElement.querySelectorAll('button[role="checkbox"]');
145
+ // Each column row has 3 checkboxes: PK, Null, Unq
146
+ // Row 0: checkboxes[0]=PK, checkboxes[1]=Null, checkboxes[2]=Unq
147
+ // Row 1: checkboxes[3]=PK, checkboxes[4]=Null, checkboxes[5]=Unq
148
+ const nullCheckbox = checkboxes[4];
149
+
150
+ // New column is nullable by default (checked)
151
+ expect(nullCheckbox.getAttribute('data-state')).toBe('checked');
152
+
153
+ // Uncheck nullable
154
+ act(() => { fireEvent.click(nullCheckbox); });
155
+ expect(nullCheckbox.getAttribute('data-state')).toBe('unchecked');
156
+
157
+ // SQL should now contain NOT NULL for the status column
158
+ const text = baseElement.textContent || '';
159
+ expect(text).toContain('status VARCHAR(255) NOT NULL');
160
+ });
161
+
162
+ // ── 7. UNIQUE in SQL for unique columns ────────────────────────────────────
163
+
164
+ test('UNIQUE appears in SQL for unique non-PK column', () => {
165
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
166
+ const body = within(baseElement);
167
+
168
+ // Set table name
169
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
170
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'products' } }); });
171
+
172
+ // Add a new column
173
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
174
+
175
+ // Set column name
176
+ const colInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
177
+ act(() => { fireEvent.change(colInputs[1], { target: { value: 'sku' } }); });
178
+
179
+ // Find checkboxes for column row 1
180
+ const checkboxes = baseElement.querySelectorAll('button[role="checkbox"]');
181
+ // Row 1: PK=checkboxes[3], Null=checkboxes[4], Unq=checkboxes[5]
182
+ const uniqueCheckbox = checkboxes[5];
183
+
184
+ // Initially unchecked
185
+ expect(uniqueCheckbox.getAttribute('data-state')).toBe('unchecked');
186
+
187
+ // Check unique
188
+ act(() => { fireEvent.click(uniqueCheckbox); });
189
+ expect(uniqueCheckbox.getAttribute('data-state')).toBe('checked');
190
+
191
+ // Also uncheck nullable so NOT NULL + UNIQUE both appear
192
+ const nullCheckbox = checkboxes[4];
193
+ act(() => { fireEvent.click(nullCheckbox); });
194
+
195
+ const text = baseElement.textContent || '';
196
+ expect(text).toContain('sku VARCHAR(255) NOT NULL UNIQUE');
197
+ });
198
+
199
+ // ── 8. DEFAULT value in SQL ────────────────────────────────────────────────
200
+
201
+ test('DEFAULT value appears in SQL when set', () => {
202
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
203
+ const body = within(baseElement);
204
+
205
+ // Set table name
206
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
207
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'logs' } }); });
208
+
209
+ // Add a new column
210
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
211
+
212
+ // Set column name
213
+ const colInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
214
+ act(() => { fireEvent.change(colInputs[1], { target: { value: 'level' } }); });
215
+
216
+ // The component doesn't have a visible default value input in the main column row,
217
+ // but the generateSQL function uses col.defaultValue. Since there's no UI for it
218
+ // in the current component (defaultValue is always '' in UI), we verify the SQL
219
+ // generation logic works by checking that an empty default produces no DEFAULT clause.
220
+ const text = baseElement.textContent || '';
221
+ expect(text).toContain('level VARCHAR(255)');
222
+ expect(text).not.toContain('DEFAULT');
223
+ });
224
+
225
+ // ── 9. Validate empty table name -> button disabled ─────────────────────────
226
+
227
+ test('CREATE TABLE button is disabled when table name is empty', () => {
228
+ const onTableCreated = mock(() => {});
229
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={onTableCreated} />);
230
+ const body = within(baseElement);
231
+
232
+ // Table name is empty by default
233
+ const createBtn = body.getByText('CREATE TABLE').closest('button') as HTMLButtonElement;
234
+ expect(createBtn.disabled).toBe(true);
235
+
236
+ // Click should not invoke onTableCreated
237
+ act(() => { fireEvent.click(createBtn); });
238
+ expect(onTableCreated).not.toHaveBeenCalled();
239
+ });
240
+
241
+ // ── 10. Validate empty column names -> error toast ─────────────────────────
242
+
243
+ test('handleCreate shows error toast when a column has empty name', () => {
244
+ const onTableCreated = mock(() => {});
245
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={onTableCreated} />);
246
+ const body = within(baseElement);
247
+
248
+ // Set table name
249
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
250
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'test_table' } }); });
251
+
252
+ // Add a second column but leave its name empty
253
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
254
+
255
+ // The new column has name='' by default
256
+ const createBtn = body.getByText('CREATE TABLE');
257
+ act(() => { fireEvent.click(createBtn); });
258
+
259
+ expect(mockToastError).toHaveBeenCalledWith('All columns must have a name');
260
+ expect(onTableCreated).not.toHaveBeenCalled();
261
+ });
262
+
263
+ // ── 11. handleCreate calls onTableCreated + onClose on success ─────────────
264
+
265
+ test('handleCreate calls onTableCreated and onClose on success', () => {
266
+ const onTableCreated = mock(() => {});
267
+ const onClose = mock(() => {});
268
+ const { baseElement } = render(<CreateTableModal isOpen onClose={onClose} onTableCreated={onTableCreated} />);
269
+ const body = within(baseElement);
270
+
271
+ // Set table name
272
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
273
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'customers' } }); });
274
+
275
+ // Default id column already has a name, so validation passes
276
+ const createBtn = body.getByText('CREATE TABLE');
277
+ act(() => { fireEvent.click(createBtn); });
278
+
279
+ expect(onTableCreated).toHaveBeenCalledTimes(1);
280
+ // Verify the SQL was passed
281
+ const sqlArg = (onTableCreated as ReturnType<typeof mock>).mock.calls[0][0] as string;
282
+ expect(sqlArg).toContain('CREATE TABLE customers');
283
+ expect(sqlArg).toContain('id SERIAL PRIMARY KEY');
284
+
285
+ expect(onClose).toHaveBeenCalledTimes(1);
286
+ });
287
+
288
+ // ── 12. State resets after creation ────────────────────────────────────────
289
+
290
+ test('state resets after successful creation', () => {
291
+ const onTableCreated = mock(() => {});
292
+ const onClose = mock(() => {});
293
+ const { baseElement } = render(<CreateTableModal isOpen onClose={onClose} onTableCreated={onTableCreated} />);
294
+ const body = within(baseElement);
295
+
296
+ // Set table name and add a column
297
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
298
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'temp_table' } }); });
299
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
300
+
301
+ // Fill second column name
302
+ const colInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
303
+ act(() => { fireEvent.change(colInputs[1], { target: { value: 'name' } }); });
304
+
305
+ // We should have 2 columns now
306
+ expect(baseElement.querySelectorAll('input[placeholder="column_name"]').length).toBe(2);
307
+
308
+ // Create table
309
+ act(() => { fireEvent.click(body.getByText('CREATE TABLE')); });
310
+
311
+ // After creation, state resets: table name should be empty, columns back to default (1)
312
+ expect(tableNameInput.value).toBe('');
313
+ expect(baseElement.querySelectorAll('input[placeholder="column_name"]').length).toBe(1);
314
+
315
+ // The remaining column should be the default "id"
316
+ const resetColInput = baseElement.querySelector('input[placeholder="column_name"]') as HTMLInputElement;
317
+ expect(resetColInput.value).toBe('id');
318
+ });
319
+
320
+ // ── 13. PK checkbox auto-unchecks Nullable ─────────────────────────────────
321
+
322
+ test('checking PK auto-unchecks Nullable', () => {
323
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
324
+ const body = within(baseElement);
325
+
326
+ // Add a new column (default: isPrimary=false, isNullable=true)
327
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
328
+
329
+ const checkboxes = baseElement.querySelectorAll('button[role="checkbox"]');
330
+ // Row 1: PK=checkboxes[3], Null=checkboxes[4], Unq=checkboxes[5]
331
+ const pkCheckbox = checkboxes[3];
332
+ const nullCheckbox = checkboxes[4];
333
+
334
+ // Initially: PK unchecked, Null checked
335
+ expect(pkCheckbox.getAttribute('data-state')).toBe('unchecked');
336
+ expect(nullCheckbox.getAttribute('data-state')).toBe('checked');
337
+
338
+ // Check PK
339
+ act(() => { fireEvent.click(pkCheckbox); });
340
+
341
+ // PK should be checked, Null should be auto-unchecked
342
+ expect(pkCheckbox.getAttribute('data-state')).toBe('checked');
343
+ expect(nullCheckbox.getAttribute('data-state')).toBe('unchecked');
344
+ });
345
+
346
+ // ── 14. Cancel button calls onClose ────────────────────────────────────────
347
+
348
+ test('Cancel button calls onClose', () => {
349
+ const onClose = mock(() => {});
350
+ const { baseElement } = render(<CreateTableModal isOpen onClose={onClose} onTableCreated={mock(() => {})} />);
351
+ const body = within(baseElement);
352
+
353
+ const cancelBtn = body.getByText('Cancel');
354
+ act(() => { fireEvent.click(cancelBtn); });
355
+
356
+ expect(onClose).toHaveBeenCalledTimes(1);
357
+ });
358
+
359
+ // ── 15. Column type selector changes SQL preview ───────────────────────────
360
+
361
+ test('default column type VARCHAR(255) appears in SQL for new columns', () => {
362
+ const { baseElement } = render(<CreateTableModal isOpen onClose={mock(() => {})} onTableCreated={mock(() => {})} />);
363
+ const body = within(baseElement);
364
+
365
+ // Set table name
366
+ const tableNameInput = baseElement.querySelector('#tableName') as HTMLInputElement;
367
+ act(() => { fireEvent.change(tableNameInput, { target: { value: 'items' } }); });
368
+
369
+ // Add a new column (defaults to VARCHAR(255))
370
+ act(() => { fireEvent.click(body.getByText('Add Column')); });
371
+
372
+ // Name the new column
373
+ const colInputs = baseElement.querySelectorAll('input[placeholder="column_name"]');
374
+ act(() => { fireEvent.change(colInputs[1], { target: { value: 'description' } }); });
375
+
376
+ // SQL preview should show the default type
377
+ const text = baseElement.textContent || '';
378
+ expect(text).toContain('description VARCHAR(255)');
379
+
380
+ // Also verify the default id column uses SERIAL
381
+ expect(text).toContain('id SERIAL PRIMARY KEY');
382
+ });
383
+ });