@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.
- package/.claude/settings.local.json +127 -0
- package/.cursorrules +426 -0
- package/.devin/wiki.json +143 -0
- package/.dockerignore +80 -0
- package/.env.example +159 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
- package/.github/workflows/ci.yml +185 -0
- package/.github/workflows/codeql.yml +57 -0
- package/.github/workflows/docker-build-push.yml +118 -0
- package/.github/workflows/helm-release.yml +113 -0
- package/CLAUDE.md +265 -0
- package/CODE_OF_CONDUCT.md +124 -0
- package/CONTRIBUTING.md +154 -0
- package/Dockerfile +73 -0
- package/LICENSE +21 -0
- package/README.md +614 -0
- package/SECURITY.md +107 -0
- package/artifacthub-repo.yml +4 -0
- package/bun.lock +1714 -0
- package/bunfig.toml +3 -0
- package/charts/libredb-studio/.helmignore +11 -0
- package/charts/libredb-studio/Chart.lock +6 -0
- package/charts/libredb-studio/Chart.yaml +50 -0
- package/charts/libredb-studio/README.md +206 -0
- package/charts/libredb-studio/templates/NOTES.txt +59 -0
- package/charts/libredb-studio/templates/_helpers.tpl +135 -0
- package/charts/libredb-studio/templates/configmap.yaml +37 -0
- package/charts/libredb-studio/templates/deployment.yaml +184 -0
- package/charts/libredb-studio/templates/hpa.yaml +32 -0
- package/charts/libredb-studio/templates/ingress.yaml +41 -0
- package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
- package/charts/libredb-studio/templates/pdb.yaml +18 -0
- package/charts/libredb-studio/templates/pvc.yaml +23 -0
- package/charts/libredb-studio/templates/secret.yaml +30 -0
- package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
- package/charts/libredb-studio/templates/service.yaml +22 -0
- package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
- package/charts/libredb-studio/values.schema.json +246 -0
- package/charts/libredb-studio/values.yaml +286 -0
- package/components.json +22 -0
- package/conductor/code_styleguides/typescript.md +43 -0
- package/conductor/product-guidelines.md +43 -0
- package/conductor/product.md +3 -0
- package/conductor/setup_state.json +1 -0
- package/conductor/tech-stack.md +39 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
- package/conductor/tracks.md +8 -0
- package/conductor/workflow.md +333 -0
- package/database-compose.yml +55 -0
- package/docker/postgres-init/01-extensions.sql +10 -0
- package/docker/postgres-init/02-sample-data.sql +585 -0
- package/docker/postgres.yml +68 -0
- package/docker-compose.yml +38 -0
- package/docs/AI_PLAN.md +74 -0
- package/docs/API_DOCS.md +875 -0
- package/docs/ARCHITECTURE.md +218 -0
- package/docs/DATABASE_PROVIDERS.md +358 -0
- package/docs/FEATURES.md +116 -0
- package/docs/HELM_CHART.md +252 -0
- package/docs/LOGIN_PAGE.md +178 -0
- package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
- package/docs/OIDC_ARCH.md +681 -0
- package/docs/OIDC_SETUP.md +322 -0
- package/docs/POSTGRES_METRICS.md +516 -0
- package/docs/QUERY_OPTIMIZATION.md +370 -0
- package/docs/SEED_CONNECTIONS.md +468 -0
- package/docs/SQL_ALIAS_COMPLETION.md +190 -0
- package/docs/STORAGE_ARCHITECTURE.md +565 -0
- package/docs/STORAGE_QUICK_SETUP.md +419 -0
- package/docs/TECHNICAL_PLAN.md +36 -0
- package/docs/THEMING.md +345 -0
- package/docs/adding-a-new-database-provider.md +642 -0
- package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
- package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
- package/docs/backlogs/002-DATA_IMPORT.md +215 -0
- package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
- package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
- package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
- package/docs/backlogs/006-DATA_MASKING.md +418 -0
- package/docs/enterprise-features.md +718 -0
- package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
- package/docs/medium-koyeb-article-en.md +215 -0
- package/docs/plans/test-plans.md +445 -0
- package/docs/releases/RELEASE.V0.3.0.md +22 -0
- package/docs/releases/RELEASE.V0.4.0.md +154 -0
- package/docs/releases/RELEASE.V0.5.0.md +252 -0
- package/docs/releases/RELEASE_v0.5.6.md +145 -0
- package/docs/releases/RELEASE_v0.6.1.md +303 -0
- package/docs/releases/RELEASE_v0.6.7.md +292 -0
- package/docs/releases/RELEASE_v0.7.0.md +332 -0
- package/docs/releases/RELEASE_v0.8.0.md +521 -0
- package/docs/sampledb/titanic.sql +1379 -0
- package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
- package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
- package/e2e/admin-dashboard.spec.ts +64 -0
- package/e2e/connection-management.spec.ts +58 -0
- package/e2e/export.spec.ts +34 -0
- package/e2e/login.spec.ts +85 -0
- package/e2e/query-execution.spec.ts +35 -0
- package/e2e/tab-management.spec.ts +64 -0
- package/eslint.config.mjs +28 -0
- package/fly.toml +43 -0
- package/next.config.ts +32 -0
- package/package.json +130 -0
- package/playwright.config.ts +34 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.svg +32 -0
- package/public/next.svg +1 -0
- package/public/screenshots/code-generator.png +0 -0
- package/public/screenshots/connection-modal.png +0 -0
- package/public/screenshots/data-profiler.png +0 -0
- package/public/screenshots/erd-diagram.png +0 -0
- package/public/screenshots/hero-editor.png +0 -0
- package/public/screenshots/nl2sql.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/render.yaml +58 -0
- package/scripts/merge-lcov.mjs +239 -0
- package/sonar-project.properties +16 -0
- package/src/app/admin/error.tsx +46 -0
- package/src/app/admin/page.tsx +10 -0
- package/src/app/api/admin/audit/route.ts +52 -0
- package/src/app/api/admin/fleet-health/route.ts +81 -0
- package/src/app/api/ai/autopilot/route.ts +105 -0
- package/src/app/api/ai/chat/route.ts +132 -0
- package/src/app/api/ai/describe-schema/route.ts +52 -0
- package/src/app/api/ai/explain/route.ts +86 -0
- package/src/app/api/ai/impact/route.ts +97 -0
- package/src/app/api/ai/index-advisor/route.ts +98 -0
- package/src/app/api/ai/nl2sql/route.ts +87 -0
- package/src/app/api/ai/query-safety/route.ts +87 -0
- package/src/app/api/auth/login/route.ts +62 -0
- package/src/app/api/auth/logout/route.ts +25 -0
- package/src/app/api/auth/me/route.ts +10 -0
- package/src/app/api/auth/oidc/callback/route.ts +82 -0
- package/src/app/api/auth/oidc/login/route.ts +43 -0
- package/src/app/api/connections/managed/route.ts +35 -0
- package/src/app/api/db/cancel/route.ts +42 -0
- package/src/app/api/db/disconnect/route.ts +28 -0
- package/src/app/api/db/health/route.ts +49 -0
- package/src/app/api/db/maintenance/route.ts +72 -0
- package/src/app/api/db/monitoring/route.ts +62 -0
- package/src/app/api/db/multi-query/route.ts +116 -0
- package/src/app/api/db/pool-stats/route.ts +37 -0
- package/src/app/api/db/profile/route.ts +144 -0
- package/src/app/api/db/provider-meta/route.ts +49 -0
- package/src/app/api/db/query/route.ts +50 -0
- package/src/app/api/db/schema/route.ts +47 -0
- package/src/app/api/db/schema-snapshot/route.ts +42 -0
- package/src/app/api/db/test-connection/route.ts +55 -0
- package/src/app/api/db/transaction/route.ts +111 -0
- package/src/app/api/storage/[collection]/route.ts +67 -0
- package/src/app/api/storage/config/route.ts +17 -0
- package/src/app/api/storage/migrate/route.ts +45 -0
- package/src/app/api/storage/route.ts +32 -0
- package/src/app/error.tsx +49 -0
- package/src/app/global-error.tsx +55 -0
- package/src/app/globals.css +146 -0
- package/src/app/icon.svg +42 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/login/login-form.tsx +301 -0
- package/src/app/login/page.tsx +11 -0
- package/src/app/monitoring/page.tsx +8 -0
- package/src/app/not-found.tsx +29 -0
- package/src/app/page.tsx +5 -0
- package/src/components/AIAutopilotPanel.tsx +238 -0
- package/src/components/CodeGenerator.tsx +271 -0
- package/src/components/CommandPalette.tsx +227 -0
- package/src/components/ConnectionModal.tsx +759 -0
- package/src/components/CreateTableModal.tsx +281 -0
- package/src/components/DataCharts.tsx +962 -0
- package/src/components/DataImportModal.tsx +582 -0
- package/src/components/DataProfiler.tsx +335 -0
- package/src/components/DatabaseDocs.tsx +251 -0
- package/src/components/MaskingSettings.tsx +414 -0
- package/src/components/MobileNav.tsx +50 -0
- package/src/components/NL2SQLPanel.tsx +281 -0
- package/src/components/PivotTable.tsx +257 -0
- package/src/components/QueryEditor.tsx +760 -0
- package/src/components/QueryHistory.tsx +344 -0
- package/src/components/QuerySafetyDialog.tsx +290 -0
- package/src/components/ResultsGrid.tsx +644 -0
- package/src/components/SaveQueryModal.tsx +104 -0
- package/src/components/SavedQueries.tsx +128 -0
- package/src/components/SchemaDiagram.tsx +473 -0
- package/src/components/SchemaDiff.tsx +473 -0
- package/src/components/SnapshotTimeline.tsx +116 -0
- package/src/components/Studio.tsx +639 -0
- package/src/components/TestDataGenerator.tsx +261 -0
- package/src/components/VisualExplain.tsx +820 -0
- package/src/components/admin/AdminDashboard.tsx +163 -0
- package/src/components/admin/tabs/AuditTab.tsx +531 -0
- package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
- package/src/components/admin/tabs/OperationsTab.tsx +646 -0
- package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
- package/src/components/admin/tabs/SecurityTab.tsx +284 -0
- package/src/components/community-section.tsx +92 -0
- package/src/components/icons/db-icons.tsx +84 -0
- package/src/components/libredb-logo.tsx +61 -0
- package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
- package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
- package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
- package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
- package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
- package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
- package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
- package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
- package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
- package/src/components/results-grid/ResultCard.tsx +111 -0
- package/src/components/results-grid/RowDetailSheet.tsx +178 -0
- package/src/components/results-grid/StatsBar.tsx +201 -0
- package/src/components/results-grid/index.ts +1 -0
- package/src/components/results-grid/utils.ts +23 -0
- package/src/components/schema-explorer/ColumnList.tsx +53 -0
- package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
- package/src/components/schema-explorer/TableItem.tsx +210 -0
- package/src/components/schema-explorer/index.ts +1 -0
- package/src/components/sidebar/ConnectionItem.tsx +105 -0
- package/src/components/sidebar/ConnectionsList.tsx +62 -0
- package/src/components/sidebar/Sidebar.tsx +130 -0
- package/src/components/sidebar/index.ts +2 -0
- package/src/components/studio/BottomPanel.tsx +286 -0
- package/src/components/studio/QueryToolbar.tsx +180 -0
- package/src/components/studio/StudioDesktopHeader.tsx +114 -0
- package/src/components/studio/StudioMobileHeader.tsx +340 -0
- package/src/components/studio/StudioTabBar.tsx +82 -0
- package/src/components/studio/index.ts +5 -0
- package/src/components/ui/accordion.tsx +66 -0
- package/src/components/ui/alert-dialog.tsx +157 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/aspect-ratio.tsx +11 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/calendar.tsx +216 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +241 -0
- package/src/components/ui/chart.tsx +357 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/context-menu.tsx +252 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +248 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/hover-card.tsx +44 -0
- package/src/components/ui/input-group.tsx +170 -0
- package/src/components/ui/input-otp.tsx +77 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/item.tsx +193 -0
- package/src/components/ui/kbd.tsx +28 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/menubar.tsx +276 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +48 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +187 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +63 -0
- package/src/components/ui/sonner.tsx +40 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +83 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/exports/components.ts +15 -0
- package/src/exports/index.ts +4 -0
- package/src/exports/providers.ts +4 -0
- package/src/exports/types.ts +26 -0
- package/src/hooks/use-ai-chat.ts +182 -0
- package/src/hooks/use-all-connections.ts +66 -0
- package/src/hooks/use-api-call.ts +71 -0
- package/src/hooks/use-auth.ts +51 -0
- package/src/hooks/use-connection-form.ts +349 -0
- package/src/hooks/use-connection-manager.ts +169 -0
- package/src/hooks/use-connection-payload.ts +15 -0
- package/src/hooks/use-inline-editing.ts +109 -0
- package/src/hooks/use-mobile.ts +20 -0
- package/src/hooks/use-monitoring-data.ts +270 -0
- package/src/hooks/use-provider-metadata.ts +62 -0
- package/src/hooks/use-query-execution.ts +478 -0
- package/src/hooks/use-storage-sync.ts +259 -0
- package/src/hooks/use-tab-manager.ts +231 -0
- package/src/hooks/use-toast.ts +20 -0
- package/src/hooks/use-transaction-control.ts +64 -0
- package/src/lib/api/error-codes.ts +30 -0
- package/src/lib/api/errors.ts +236 -0
- package/src/lib/api/with-error-handler.ts +41 -0
- package/src/lib/audit.ts +105 -0
- package/src/lib/auth.ts +87 -0
- package/src/lib/connection-string-parser.ts +172 -0
- package/src/lib/data-masking.ts +385 -0
- package/src/lib/db/base-provider.ts +325 -0
- package/src/lib/db/errors.ts +317 -0
- package/src/lib/db/factory.ts +324 -0
- package/src/lib/db/index.ts +123 -0
- package/src/lib/db/providers/document/index.ts +6 -0
- package/src/lib/db/providers/document/mongodb.ts +992 -0
- package/src/lib/db/providers/keyvalue/redis.ts +554 -0
- package/src/lib/db/providers/sql/index.ts +11 -0
- package/src/lib/db/providers/sql/mssql.ts +1065 -0
- package/src/lib/db/providers/sql/mysql.ts +978 -0
- package/src/lib/db/providers/sql/oracle.ts +1044 -0
- package/src/lib/db/providers/sql/postgres.ts +1179 -0
- package/src/lib/db/providers/sql/sql-base.ts +174 -0
- package/src/lib/db/providers/sql/sqlite.ts +721 -0
- package/src/lib/db/types.ts +437 -0
- package/src/lib/db/utils/pool-manager.ts +287 -0
- package/src/lib/db/utils/query-limiter.ts +239 -0
- package/src/lib/db-ui-config.ts +86 -0
- package/src/lib/editor/mongodb-completions.ts +172 -0
- package/src/lib/editor/sql-completions.ts +280 -0
- package/src/lib/llm/base-provider.ts +117 -0
- package/src/lib/llm/factory.ts +102 -0
- package/src/lib/llm/index.ts +90 -0
- package/src/lib/llm/providers/custom.ts +181 -0
- package/src/lib/llm/providers/gemini.ts +126 -0
- package/src/lib/llm/providers/ollama.ts +154 -0
- package/src/lib/llm/providers/openai.ts +146 -0
- package/src/lib/llm/types.ts +173 -0
- package/src/lib/llm/utils/config.ts +187 -0
- package/src/lib/llm/utils/retry.ts +119 -0
- package/src/lib/llm/utils/streaming.ts +202 -0
- package/src/lib/logger.ts +127 -0
- package/src/lib/monitoring-thresholds.ts +44 -0
- package/src/lib/oidc.ts +262 -0
- package/src/lib/query-generators.ts +61 -0
- package/src/lib/schema-diff/diff-engine.ts +273 -0
- package/src/lib/schema-diff/migration-generator.ts +208 -0
- package/src/lib/schema-diff/types.ts +55 -0
- package/src/lib/seed/config-loader.ts +79 -0
- package/src/lib/seed/connection-filter.ts +49 -0
- package/src/lib/seed/credential-resolver.ts +62 -0
- package/src/lib/seed/index.ts +40 -0
- package/src/lib/seed/resolve-connection.ts +57 -0
- package/src/lib/seed/types.ts +69 -0
- package/src/lib/sql/alias-extractor.ts +267 -0
- package/src/lib/sql/index.ts +8 -0
- package/src/lib/sql/statement-splitter.ts +167 -0
- package/src/lib/sql/types.ts +40 -0
- package/src/lib/ssh/tunnel.ts +142 -0
- package/src/lib/storage/factory.ts +84 -0
- package/src/lib/storage/index.ts +14 -0
- package/src/lib/storage/local-storage.ts +99 -0
- package/src/lib/storage/providers/postgres.ts +225 -0
- package/src/lib/storage/providers/sqlite.ts +153 -0
- package/src/lib/storage/storage-facade.ts +272 -0
- package/src/lib/storage/types.ts +75 -0
- package/src/lib/time-series-buffer.ts +58 -0
- package/src/lib/types.ts +173 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +104 -0
- package/src/types/db-drivers.d.ts +23 -0
- package/src/types/html2canvas.d.ts +9 -0
- package/tests/api/admin/audit.test.ts +178 -0
- package/tests/api/admin/fleet-health.test.ts +183 -0
- package/tests/api/ai/autopilot.test.ts +174 -0
- package/tests/api/ai/chat.test.ts +250 -0
- package/tests/api/ai/describe-schema.test.ts +266 -0
- package/tests/api/ai/explain.test.ts +199 -0
- package/tests/api/ai/impact.test.ts +168 -0
- package/tests/api/ai/index-advisor.test.ts +171 -0
- package/tests/api/ai/nl2sql.test.ts +202 -0
- package/tests/api/ai/query-safety.test.ts +196 -0
- package/tests/api/auth/login.test.ts +170 -0
- package/tests/api/auth/logout.test.ts +140 -0
- package/tests/api/auth/me.test.ts +73 -0
- package/tests/api/auth/oidc-callback.test.ts +215 -0
- package/tests/api/auth/oidc-login.test.ts +127 -0
- package/tests/api/db/cancel.test.ts +198 -0
- package/tests/api/db/disconnect.test.ts +124 -0
- package/tests/api/db/health.test.ts +222 -0
- package/tests/api/db/maintenance.test.ts +263 -0
- package/tests/api/db/monitoring.test.ts +221 -0
- package/tests/api/db/multi-query.test.ts +316 -0
- package/tests/api/db/pool-stats.test.ts +135 -0
- package/tests/api/db/profile.test.ts +330 -0
- package/tests/api/db/provider-meta.test.ts +193 -0
- package/tests/api/db/query.test.ts +314 -0
- package/tests/api/db/schema-snapshot.test.ts +170 -0
- package/tests/api/db/schema.test.ts +191 -0
- package/tests/api/db/test-connection.test.ts +185 -0
- package/tests/api/db/transaction.test.ts +314 -0
- package/tests/api/proxy.test.ts +191 -0
- package/tests/api/seed/managed-route.test.ts +113 -0
- package/tests/api/storage/config.test.ts +42 -0
- package/tests/api/storage/storage-routes.test.ts +309 -0
- package/tests/components/AIAutopilotPanel.test.tsx +756 -0
- package/tests/components/AdminPage.test.tsx +33 -0
- package/tests/components/CodeGenerator.test.tsx +182 -0
- package/tests/components/CommandPalette.test.tsx +428 -0
- package/tests/components/CommunitySection.test.tsx +91 -0
- package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
- package/tests/components/ConnectionModal.test.tsx +570 -0
- package/tests/components/CreateTableModal.test.tsx +383 -0
- package/tests/components/DataCharts.test.tsx +739 -0
- package/tests/components/DataImportModal.test.tsx +751 -0
- package/tests/components/DataProfiler.test.tsx +589 -0
- package/tests/components/DatabaseDocs.test.tsx +353 -0
- package/tests/components/LoginPage.test.tsx +163 -0
- package/tests/components/LoginPageOIDC.test.tsx +92 -0
- package/tests/components/MaskingSettings.test.tsx +498 -0
- package/tests/components/MobileNav.test.tsx +30 -0
- package/tests/components/MonitoringPage.test.tsx +32 -0
- package/tests/components/NL2SQLPanel.test.tsx +621 -0
- package/tests/components/Page.test.tsx +33 -0
- package/tests/components/PivotTable.test.tsx +350 -0
- package/tests/components/QueryEditor.test.tsx +1730 -0
- package/tests/components/QueryHistory.test.tsx +572 -0
- package/tests/components/QuerySafetyDialog.test.tsx +586 -0
- package/tests/components/ResultsGrid.test.tsx +804 -0
- package/tests/components/RootLayout.test.tsx +83 -0
- package/tests/components/SaveQueryModal.test.tsx +25 -0
- package/tests/components/SavedQueries.test.tsx +43 -0
- package/tests/components/SchemaDiagram.test.tsx +1034 -0
- package/tests/components/SchemaDiff.test.tsx +906 -0
- package/tests/components/SnapshotTimeline.test.tsx +174 -0
- package/tests/components/Studio.test.tsx +1030 -0
- package/tests/components/TestDataGenerator.test.tsx +291 -0
- package/tests/components/VisualExplain.test.tsx +704 -0
- package/tests/components/admin/AdminDashboard.test.tsx +205 -0
- package/tests/components/admin/AuditTab.test.tsx +220 -0
- package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
- package/tests/components/admin/OperationsTab.test.tsx +975 -0
- package/tests/components/admin/OverviewTab.test.tsx +254 -0
- package/tests/components/admin/SecurityTab.test.tsx +467 -0
- package/tests/components/monitoring/MetricChart.test.tsx +111 -0
- package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
- package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
- package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
- package/tests/components/monitoring/PoolTab.test.tsx +42 -0
- package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
- package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
- package/tests/components/monitoring/StorageTab.test.tsx +127 -0
- package/tests/components/monitoring/TablesTab.test.tsx +153 -0
- package/tests/components/results-grid/ResultCard.test.tsx +105 -0
- package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
- package/tests/components/results-grid/StatsBar.test.tsx +162 -0
- package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
- package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
- package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
- package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
- package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
- package/tests/components/sidebar/Sidebar.test.tsx +187 -0
- package/tests/components/studio/BottomPanel.test.tsx +383 -0
- package/tests/components/studio/QueryToolbar.test.tsx +321 -0
- package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
- package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
- package/tests/components/studio/StudioTabBar.test.tsx +331 -0
- package/tests/fixtures/connections.ts +96 -0
- package/tests/fixtures/masking-configs.ts +86 -0
- package/tests/fixtures/query-results.ts +71 -0
- package/tests/fixtures/schemas.ts +64 -0
- package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
- package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
- package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
- package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
- package/tests/fixtures/seed-connections/valid-config.json +15 -0
- package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
- package/tests/helpers/mock-fetch.ts +59 -0
- package/tests/helpers/mock-monaco.ts +112 -0
- package/tests/helpers/mock-navigation.ts +28 -0
- package/tests/helpers/mock-next.ts +80 -0
- package/tests/helpers/mock-provider.ts +133 -0
- package/tests/helpers/mock-sonner.ts +29 -0
- package/tests/helpers/render-with-providers.tsx +19 -0
- package/tests/hooks/use-ai-chat.test.ts +600 -0
- package/tests/hooks/use-auth.test.ts +371 -0
- package/tests/hooks/use-connection-form.test.ts +743 -0
- package/tests/hooks/use-connection-manager.test.ts +466 -0
- package/tests/hooks/use-inline-editing.test.ts +321 -0
- package/tests/hooks/use-mobile.test.ts +177 -0
- package/tests/hooks/use-monitoring-data.test.ts +819 -0
- package/tests/hooks/use-provider-metadata.test.ts +228 -0
- package/tests/hooks/use-query-execution.test.ts +1212 -0
- package/tests/hooks/use-tab-manager.test.ts +756 -0
- package/tests/hooks/use-toast.test.ts +74 -0
- package/tests/hooks/use-transaction-control.test.ts +211 -0
- package/tests/integration/db/mongodb-provider.test.ts +698 -0
- package/tests/integration/db/mssql-provider.test.ts +840 -0
- package/tests/integration/db/mysql-provider.test.ts +872 -0
- package/tests/integration/db/oracle-provider.test.ts +843 -0
- package/tests/integration/db/postgres-provider.test.ts +1382 -0
- package/tests/integration/db/redis-provider.test.ts +526 -0
- package/tests/integration/db/sqlite-provider.test.ts +480 -0
- package/tests/integration/seed/seed-pipeline.test.ts +102 -0
- package/tests/isolated/factory-singleton.test.ts +150 -0
- package/tests/isolated/use-storage-sync.test.ts +389 -0
- package/tests/run-components.sh +196 -0
- package/tests/setup-dom.ts +58 -0
- package/tests/setup.ts +40 -0
- package/tests/unit/api-errors.test.ts +210 -0
- package/tests/unit/code-generator-functions.test.ts +271 -0
- package/tests/unit/components/column-list.test.tsx +190 -0
- package/tests/unit/components/data-import-modal.test.tsx +441 -0
- package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
- package/tests/unit/data-charts-functions.test.ts +496 -0
- package/tests/unit/data-import-functions.test.ts +320 -0
- package/tests/unit/data-import-utils.test.ts +125 -0
- package/tests/unit/db/base-provider.test.ts +517 -0
- package/tests/unit/db/errors.test.ts +403 -0
- package/tests/unit/db/factory.test.ts +436 -0
- package/tests/unit/db/pool-manager.test.ts +440 -0
- package/tests/unit/db/query-limiter.test.ts +387 -0
- package/tests/unit/db/sql-base.test.ts +438 -0
- package/tests/unit/lib/api/error-codes.test.ts +39 -0
- package/tests/unit/lib/audit.test.ts +326 -0
- package/tests/unit/lib/auth.test.ts +146 -0
- package/tests/unit/lib/connection-string-parser.test.ts +424 -0
- package/tests/unit/lib/data-masking.test.ts +583 -0
- package/tests/unit/lib/db-icons.test.tsx +41 -0
- package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
- package/tests/unit/lib/oidc.test.ts +509 -0
- package/tests/unit/lib/query-generators.test.ts +127 -0
- package/tests/unit/lib/storage/factory.test.ts +71 -0
- package/tests/unit/lib/storage/local-storage.test.ts +114 -0
- package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
- package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
- package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
- package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
- package/tests/unit/lib/storage.test.ts +317 -0
- package/tests/unit/lib/time-series-buffer.test.ts +212 -0
- package/tests/unit/lib/utils.test.ts +24 -0
- package/tests/unit/llm/base-provider.test.ts +238 -0
- package/tests/unit/llm/config.test.ts +262 -0
- package/tests/unit/llm/custom-provider.test.ts +281 -0
- package/tests/unit/llm/gemini-provider.test.ts +248 -0
- package/tests/unit/llm/llm-factory.test.ts +155 -0
- package/tests/unit/llm/ollama-provider.test.ts +288 -0
- package/tests/unit/llm/openai-provider.test.ts +324 -0
- package/tests/unit/llm/retry.test.ts +180 -0
- package/tests/unit/llm/streaming.test.ts +355 -0
- package/tests/unit/logger.test.ts +198 -0
- package/tests/unit/mongodb-completions.test.ts +516 -0
- package/tests/unit/pivot-table-functions.test.ts +76 -0
- package/tests/unit/query-cancelled-error.test.ts +81 -0
- package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
- package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
- package/tests/unit/seed/config-loader.test.ts +73 -0
- package/tests/unit/seed/connection-filter.test.ts +91 -0
- package/tests/unit/seed/credential-resolver.test.ts +85 -0
- package/tests/unit/seed/index.test.ts +72 -0
- package/tests/unit/seed/resolve-connection.test.ts +74 -0
- package/tests/unit/seed/types.test.ts +129 -0
- package/tests/unit/sql/alias-extractor.test.ts +444 -0
- package/tests/unit/sql/statement-splitter.test.ts +348 -0
- package/tests/unit/sql-completions.test.ts +463 -0
- package/tests/unit/ssh-tunnel.test.ts +465 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import '../../setup-dom';
|
|
2
|
+
import { mockToastSuccess } from '../../helpers/mock-sonner';
|
|
3
|
+
import '../../helpers/mock-navigation';
|
|
4
|
+
|
|
5
|
+
import { mock } from 'bun:test';
|
|
6
|
+
import { setupRechartssMock, setupFramerMotionMock } from '../../helpers/mock-monaco';
|
|
7
|
+
|
|
8
|
+
setupRechartssMock();
|
|
9
|
+
setupFramerMotionMock();
|
|
10
|
+
|
|
11
|
+
mock.module('@/components/MaskingSettings', () => ({
|
|
12
|
+
MaskingSettings: () => {
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
|
+
const React = require('react');
|
|
15
|
+
return React.createElement('div', { 'data-testid': 'masking-settings' }, 'MaskingSettings');
|
|
16
|
+
},
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
mock.module('@/lib/monitoring-thresholds', () => ({
|
|
20
|
+
DEFAULT_THRESHOLDS: [
|
|
21
|
+
{ metric: 'cacheHitRatio', warning: 90, critical: 80, direction: 'below' as const, label: 'Cache Hit Ratio' },
|
|
22
|
+
{ metric: 'connectionPercent', warning: 70, critical: 90, direction: 'above' as const, label: 'Connection Usage' },
|
|
23
|
+
],
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
27
|
+
import { render, fireEvent, act, waitFor, cleanup } from '@testing-library/react';
|
|
28
|
+
import React from 'react';
|
|
29
|
+
|
|
30
|
+
import { SecurityTab } from '@/components/admin/tabs/SecurityTab';
|
|
31
|
+
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// SecurityTab Tests
|
|
34
|
+
// =============================================================================
|
|
35
|
+
|
|
36
|
+
// Helper: Radix Tabs uses onMouseDown (not onClick) to switch tabs.
|
|
37
|
+
// In happy-dom, fireEvent.click does not trigger onMouseDown in the correct
|
|
38
|
+
// event sequence, so we dispatch mouseDown explicitly.
|
|
39
|
+
function clickRadixTab(element: HTMLElement) {
|
|
40
|
+
fireEvent.mouseDown(element, { button: 0 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('SecurityTab', () => {
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
cleanup();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
// Clear localStorage between tests
|
|
50
|
+
if (typeof localStorage !== 'undefined') {
|
|
51
|
+
localStorage.removeItem('libredb_threshold_config');
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('renders 3 tabs (Data Masking, Access, Thresholds)', async () => {
|
|
56
|
+
let renderResult: ReturnType<typeof render>;
|
|
57
|
+
await act(async () => {
|
|
58
|
+
renderResult = render(<SecurityTab />);
|
|
59
|
+
});
|
|
60
|
+
const { queryByText } = renderResult!;
|
|
61
|
+
|
|
62
|
+
expect(queryByText('Data Masking')).not.toBeNull();
|
|
63
|
+
expect(queryByText('Access')).not.toBeNull();
|
|
64
|
+
expect(queryByText('Thresholds')).not.toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('masking tab shows MaskingSettings component', async () => {
|
|
68
|
+
let renderResult: ReturnType<typeof render>;
|
|
69
|
+
await act(async () => {
|
|
70
|
+
renderResult = render(<SecurityTab />);
|
|
71
|
+
});
|
|
72
|
+
const { queryByTestId, queryByText } = renderResult!;
|
|
73
|
+
|
|
74
|
+
// Data Masking is the default tab, so MaskingSettings should be visible
|
|
75
|
+
expect(queryByTestId('masking-settings')).not.toBeNull();
|
|
76
|
+
expect(queryByText('MaskingSettings')).not.toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('access tab shows security summary cards', async () => {
|
|
80
|
+
let renderResult: ReturnType<typeof render>;
|
|
81
|
+
await act(async () => {
|
|
82
|
+
renderResult = render(<SecurityTab />);
|
|
83
|
+
});
|
|
84
|
+
const { getByText, queryByText } = renderResult!;
|
|
85
|
+
|
|
86
|
+
// Click the Access tab using mouseDown (Radix listens to onMouseDown)
|
|
87
|
+
await act(async () => {
|
|
88
|
+
clickRadixTab(getByText('Access'));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Access summary shows these labels
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
expect(queryByText('Security & Access')).not.toBeNull();
|
|
94
|
+
expect(queryByText('Authentication')).not.toBeNull();
|
|
95
|
+
expect(queryByText('API Security')).not.toBeNull();
|
|
96
|
+
expect(queryByText('Connection Security')).not.toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('thresholds tab shows metric sliders', async () => {
|
|
101
|
+
let renderResult: ReturnType<typeof render>;
|
|
102
|
+
await act(async () => {
|
|
103
|
+
renderResult = render(<SecurityTab />);
|
|
104
|
+
});
|
|
105
|
+
const { getByText, queryByText } = renderResult!;
|
|
106
|
+
|
|
107
|
+
// Click the Thresholds tab using mouseDown (Radix listens to onMouseDown)
|
|
108
|
+
await act(async () => {
|
|
109
|
+
clickRadixTab(getByText('Thresholds'));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Threshold settings should show metric labels
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
expect(queryByText('Monitoring Thresholds')).not.toBeNull();
|
|
115
|
+
expect(queryByText('Cache Hit Ratio')).not.toBeNull();
|
|
116
|
+
expect(queryByText('Connection Usage')).not.toBeNull();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('save button present in thresholds', async () => {
|
|
121
|
+
let renderResult: ReturnType<typeof render>;
|
|
122
|
+
await act(async () => {
|
|
123
|
+
renderResult = render(<SecurityTab />);
|
|
124
|
+
});
|
|
125
|
+
const { getByText, queryByText } = renderResult!;
|
|
126
|
+
|
|
127
|
+
// Click the Thresholds tab using mouseDown (Radix listens to onMouseDown)
|
|
128
|
+
await act(async () => {
|
|
129
|
+
clickRadixTab(getByText('Thresholds'));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await waitFor(() => {
|
|
133
|
+
expect(queryByText('Save Config')).not.toBeNull();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('reset button present in thresholds', async () => {
|
|
138
|
+
let renderResult: ReturnType<typeof render>;
|
|
139
|
+
await act(async () => {
|
|
140
|
+
renderResult = render(<SecurityTab />);
|
|
141
|
+
});
|
|
142
|
+
const { getByText, queryByText } = renderResult!;
|
|
143
|
+
|
|
144
|
+
// Click the Thresholds tab using mouseDown (Radix listens to onMouseDown)
|
|
145
|
+
await act(async () => {
|
|
146
|
+
clickRadixTab(getByText('Thresholds'));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await waitFor(() => {
|
|
150
|
+
expect(queryByText('Reset Defaults')).not.toBeNull();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ===========================================================================
|
|
155
|
+
// NEW TESTS
|
|
156
|
+
// ===========================================================================
|
|
157
|
+
|
|
158
|
+
test('access tab shows security badges (ENABLED, Supported, Configurable)', async () => {
|
|
159
|
+
let renderResult: ReturnType<typeof render>;
|
|
160
|
+
await act(async () => {
|
|
161
|
+
renderResult = render(<SecurityTab />);
|
|
162
|
+
});
|
|
163
|
+
const { getByText, queryAllByText, queryByText } = renderResult!;
|
|
164
|
+
|
|
165
|
+
await act(async () => {
|
|
166
|
+
clickRadixTab(getByText('Access'));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
// Admin Access and User Access both have ENABLED badges
|
|
171
|
+
const enabledBadges = queryAllByText('ENABLED');
|
|
172
|
+
expect(enabledBadges.length).toBe(2);
|
|
173
|
+
|
|
174
|
+
// SSL/TLS and SSH Tunnel have Supported badges
|
|
175
|
+
const supportedBadges = queryAllByText('Supported');
|
|
176
|
+
expect(supportedBadges.length).toBe(2);
|
|
177
|
+
|
|
178
|
+
// Data Masking has Configurable badge
|
|
179
|
+
expect(queryByText('Configurable')).not.toBeNull();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('access tab shows JWT and RBAC info', async () => {
|
|
184
|
+
let renderResult: ReturnType<typeof render>;
|
|
185
|
+
await act(async () => {
|
|
186
|
+
renderResult = render(<SecurityTab />);
|
|
187
|
+
});
|
|
188
|
+
const { getByText, queryByText } = renderResult!;
|
|
189
|
+
|
|
190
|
+
await act(async () => {
|
|
191
|
+
clickRadixTab(getByText('Access'));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await waitFor(() => {
|
|
195
|
+
// Authentication method shows RBAC
|
|
196
|
+
expect(queryByText('Environment Variable (RBAC)')).not.toBeNull();
|
|
197
|
+
// API security shows JWT
|
|
198
|
+
expect(queryByText('JWT / HTTP-only Cookie')).not.toBeNull();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('thresholds tab shows direction text (Alert when above/below)', async () => {
|
|
203
|
+
let renderResult: ReturnType<typeof render>;
|
|
204
|
+
await act(async () => {
|
|
205
|
+
renderResult = render(<SecurityTab />);
|
|
206
|
+
});
|
|
207
|
+
const { getByText, queryByText } = renderResult!;
|
|
208
|
+
|
|
209
|
+
await act(async () => {
|
|
210
|
+
clickRadixTab(getByText('Thresholds'));
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await waitFor(() => {
|
|
214
|
+
// cacheHitRatio has direction 'below'
|
|
215
|
+
expect(queryByText('Alert when below')).not.toBeNull();
|
|
216
|
+
// connectionPercent has direction 'above'
|
|
217
|
+
expect(queryByText('Alert when above')).not.toBeNull();
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('save button is disabled when no changes have been made', async () => {
|
|
222
|
+
let renderResult: ReturnType<typeof render>;
|
|
223
|
+
await act(async () => {
|
|
224
|
+
renderResult = render(<SecurityTab />);
|
|
225
|
+
});
|
|
226
|
+
const { getByText } = renderResult!;
|
|
227
|
+
|
|
228
|
+
await act(async () => {
|
|
229
|
+
clickRadixTab(getByText('Thresholds'));
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
const saveButton = getByText('Save Config').closest('button');
|
|
234
|
+
expect(saveButton).not.toBeNull();
|
|
235
|
+
expect(saveButton!.disabled).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('save button becomes enabled after slider change', async () => {
|
|
240
|
+
let renderResult: ReturnType<typeof render>;
|
|
241
|
+
await act(async () => {
|
|
242
|
+
renderResult = render(<SecurityTab />);
|
|
243
|
+
});
|
|
244
|
+
const { getByText, container } = renderResult!;
|
|
245
|
+
|
|
246
|
+
await act(async () => {
|
|
247
|
+
clickRadixTab(getByText('Thresholds'));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Find a slider thumb and press ArrowRight to change the value
|
|
251
|
+
await waitFor(() => {
|
|
252
|
+
const sliders = container.querySelectorAll('[role="slider"]');
|
|
253
|
+
expect(sliders.length).toBeGreaterThan(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const sliders = container.querySelectorAll('[role="slider"]');
|
|
257
|
+
await act(async () => {
|
|
258
|
+
(sliders[0] as HTMLElement).focus();
|
|
259
|
+
fireEvent.keyDown(sliders[0], { key: 'ArrowRight' });
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await waitFor(() => {
|
|
263
|
+
const saveButton = getByText('Save Config').closest('button');
|
|
264
|
+
expect(saveButton).not.toBeNull();
|
|
265
|
+
expect(saveButton!.disabled).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('save config writes to localStorage and shows success toast', async () => {
|
|
270
|
+
mockToastSuccess.mockClear();
|
|
271
|
+
|
|
272
|
+
let renderResult: ReturnType<typeof render>;
|
|
273
|
+
await act(async () => {
|
|
274
|
+
renderResult = render(<SecurityTab />);
|
|
275
|
+
});
|
|
276
|
+
const { getByText, container } = renderResult!;
|
|
277
|
+
|
|
278
|
+
await act(async () => {
|
|
279
|
+
clickRadixTab(getByText('Thresholds'));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Change a slider value so Save becomes enabled
|
|
283
|
+
await waitFor(() => {
|
|
284
|
+
const sliders = container.querySelectorAll('[role="slider"]');
|
|
285
|
+
expect(sliders.length).toBeGreaterThan(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const sliders = container.querySelectorAll('[role="slider"]');
|
|
289
|
+
await act(async () => {
|
|
290
|
+
(sliders[0] as HTMLElement).focus();
|
|
291
|
+
fireEvent.keyDown(sliders[0], { key: 'ArrowRight' });
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Click Save Config
|
|
295
|
+
await act(async () => {
|
|
296
|
+
fireEvent.click(getByText('Save Config'));
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Verify localStorage was written
|
|
300
|
+
const stored = localStorage.getItem('libredb_threshold_config');
|
|
301
|
+
expect(stored).not.toBeNull();
|
|
302
|
+
const parsed = JSON.parse(stored!);
|
|
303
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
304
|
+
expect(parsed.length).toBe(2);
|
|
305
|
+
|
|
306
|
+
// Verify success toast
|
|
307
|
+
expect(mockToastSuccess).toHaveBeenCalledWith('Threshold configuration saved');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('reset defaults restores values and shows success toast', async () => {
|
|
311
|
+
mockToastSuccess.mockClear();
|
|
312
|
+
|
|
313
|
+
// Pre-seed localStorage with custom thresholds
|
|
314
|
+
const custom = [
|
|
315
|
+
{ metric: 'cacheHitRatio', warning: 50, critical: 30, direction: 'below', label: 'Cache Hit Ratio' },
|
|
316
|
+
{ metric: 'connectionPercent', warning: 60, critical: 85, direction: 'above', label: 'Connection Usage' },
|
|
317
|
+
];
|
|
318
|
+
localStorage.setItem('libredb_threshold_config', JSON.stringify(custom));
|
|
319
|
+
|
|
320
|
+
let renderResult: ReturnType<typeof render>;
|
|
321
|
+
await act(async () => {
|
|
322
|
+
renderResult = render(<SecurityTab />);
|
|
323
|
+
});
|
|
324
|
+
const { getByText, queryByText } = renderResult!;
|
|
325
|
+
|
|
326
|
+
await act(async () => {
|
|
327
|
+
clickRadixTab(getByText('Thresholds'));
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Click Reset Defaults
|
|
331
|
+
await act(async () => {
|
|
332
|
+
fireEvent.click(getByText('Reset Defaults'));
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Verify default values are shown (from the mock DEFAULT_THRESHOLDS)
|
|
336
|
+
await waitFor(() => {
|
|
337
|
+
// Cache Hit Ratio warning=90, critical=80 (defaults)
|
|
338
|
+
expect(queryByText('Cache Hit Ratio')).not.toBeNull();
|
|
339
|
+
expect(queryByText('Connection Usage')).not.toBeNull();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Verify localStorage was overwritten with defaults
|
|
343
|
+
const stored = localStorage.getItem('libredb_threshold_config');
|
|
344
|
+
expect(stored).not.toBeNull();
|
|
345
|
+
const parsed = JSON.parse(stored!);
|
|
346
|
+
expect(parsed[0].warning).toBe(90);
|
|
347
|
+
expect(parsed[0].critical).toBe(80);
|
|
348
|
+
|
|
349
|
+
// Verify success toast
|
|
350
|
+
expect(mockToastSuccess).toHaveBeenCalledWith('Thresholds reset to defaults');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('loads thresholds from localStorage on mount', async () => {
|
|
354
|
+
// Pre-seed localStorage with custom values
|
|
355
|
+
const custom = [
|
|
356
|
+
{ metric: 'cacheHitRatio', warning: 55, critical: 40, direction: 'below', label: 'Cache Hit Ratio' },
|
|
357
|
+
{ metric: 'connectionPercent', warning: 65, critical: 88, direction: 'above', label: 'Connection Usage' },
|
|
358
|
+
];
|
|
359
|
+
localStorage.setItem('libredb_threshold_config', JSON.stringify(custom));
|
|
360
|
+
|
|
361
|
+
let renderResult: ReturnType<typeof render>;
|
|
362
|
+
await act(async () => {
|
|
363
|
+
renderResult = render(<SecurityTab />);
|
|
364
|
+
});
|
|
365
|
+
const { getByText, queryByText } = renderResult!;
|
|
366
|
+
|
|
367
|
+
await act(async () => {
|
|
368
|
+
clickRadixTab(getByText('Thresholds'));
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// The custom warning/critical values should appear in the rendered output
|
|
372
|
+
await waitFor(() => {
|
|
373
|
+
// cacheHitRatio warning=55 should be displayed
|
|
374
|
+
expect(queryByText('55%')).not.toBeNull();
|
|
375
|
+
// cacheHitRatio critical=40 should be displayed
|
|
376
|
+
expect(queryByText('40%')).not.toBeNull();
|
|
377
|
+
// connectionPercent warning=65 should be displayed
|
|
378
|
+
expect(queryByText('65%')).not.toBeNull();
|
|
379
|
+
// connectionPercent critical=88 should be displayed
|
|
380
|
+
expect(queryByText('88%')).not.toBeNull();
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test('percentage suffix is shown for percent metrics', async () => {
|
|
385
|
+
let renderResult: ReturnType<typeof render>;
|
|
386
|
+
await act(async () => {
|
|
387
|
+
renderResult = render(<SecurityTab />);
|
|
388
|
+
});
|
|
389
|
+
const { getByText, queryAllByText, queryByText } = renderResult!;
|
|
390
|
+
|
|
391
|
+
await act(async () => {
|
|
392
|
+
clickRadixTab(getByText('Thresholds'));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Both mock thresholds are percent metrics (not 'deadlocks')
|
|
396
|
+
// cacheHitRatio: warning=90, critical=80
|
|
397
|
+
// connectionPercent: warning=70, critical=90
|
|
398
|
+
await waitFor(() => {
|
|
399
|
+
// 90% appears twice (cacheHitRatio warning + connectionPercent critical)
|
|
400
|
+
const ninetyPercent = queryAllByText('90%');
|
|
401
|
+
expect(ninetyPercent.length).toBe(2);
|
|
402
|
+
|
|
403
|
+
// 80% appears once (cacheHitRatio critical)
|
|
404
|
+
expect(queryByText('80%')).not.toBeNull();
|
|
405
|
+
|
|
406
|
+
// 70% appears once (connectionPercent warning)
|
|
407
|
+
expect(queryByText('70%')).not.toBeNull();
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('warning and critical slider labels render', async () => {
|
|
412
|
+
let renderResult: ReturnType<typeof render>;
|
|
413
|
+
await act(async () => {
|
|
414
|
+
renderResult = render(<SecurityTab />);
|
|
415
|
+
});
|
|
416
|
+
const { getByText, queryAllByText } = renderResult!;
|
|
417
|
+
|
|
418
|
+
await act(async () => {
|
|
419
|
+
clickRadixTab(getByText('Thresholds'));
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
await waitFor(() => {
|
|
423
|
+
// Each metric has a Warning and Critical label (2 metrics = 2 of each)
|
|
424
|
+
const warningLabels = queryAllByText('Warning');
|
|
425
|
+
expect(warningLabels.length).toBe(2);
|
|
426
|
+
|
|
427
|
+
const criticalLabels = queryAllByText('Critical');
|
|
428
|
+
expect(criticalLabels.length).toBe(2);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('thresholds tab renders all metric rows with full structure', async () => {
|
|
433
|
+
let renderResult: ReturnType<typeof render>;
|
|
434
|
+
await act(async () => {
|
|
435
|
+
renderResult = render(<SecurityTab />);
|
|
436
|
+
});
|
|
437
|
+
const { getByText, container, queryByText } = renderResult!;
|
|
438
|
+
|
|
439
|
+
await act(async () => {
|
|
440
|
+
clickRadixTab(getByText('Thresholds'));
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
await waitFor(() => {
|
|
444
|
+
// Title and description are present
|
|
445
|
+
expect(queryByText('Monitoring Thresholds')).not.toBeNull();
|
|
446
|
+
expect(
|
|
447
|
+
queryByText(/Configure warning and critical thresholds/)
|
|
448
|
+
).not.toBeNull();
|
|
449
|
+
|
|
450
|
+
// Both metric labels
|
|
451
|
+
expect(queryByText('Cache Hit Ratio')).not.toBeNull();
|
|
452
|
+
expect(queryByText('Connection Usage')).not.toBeNull();
|
|
453
|
+
|
|
454
|
+
// Both direction texts
|
|
455
|
+
expect(queryByText('Alert when below')).not.toBeNull();
|
|
456
|
+
expect(queryByText('Alert when above')).not.toBeNull();
|
|
457
|
+
|
|
458
|
+
// Slider elements: 2 metrics x 2 sliders (warning + critical) = 4 slider thumbs
|
|
459
|
+
const sliderThumbs = container.querySelectorAll('[role="slider"]');
|
|
460
|
+
expect(sliderThumbs.length).toBe(4);
|
|
461
|
+
|
|
462
|
+
// Both action buttons
|
|
463
|
+
expect(queryByText('Save Config')).not.toBeNull();
|
|
464
|
+
expect(queryByText('Reset Defaults')).not.toBeNull();
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import '../../setup-dom';
|
|
2
|
+
import { mock } from 'bun:test';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
// Mock recharts — DOM-only environment can't render SVG charts
|
|
6
|
+
mock.module('recharts', () => ({
|
|
7
|
+
AreaChart: ({ children, data }: { children: React.ReactNode; data: unknown[] }) =>
|
|
8
|
+
React.createElement('div', { 'data-testid': 'area-chart', 'data-count': data.length }, children),
|
|
9
|
+
Area: (props: Record<string, unknown>) =>
|
|
10
|
+
React.createElement('div', { 'data-testid': 'area', 'data-color': props.stroke }),
|
|
11
|
+
XAxis: () => React.createElement('div', { 'data-testid': 'x-axis' }),
|
|
12
|
+
YAxis: () => React.createElement('div', { 'data-testid': 'y-axis' }),
|
|
13
|
+
ResponsiveContainer: ({ children }: { children: React.ReactNode }) =>
|
|
14
|
+
React.createElement('div', { 'data-testid': 'responsive-container' }, children),
|
|
15
|
+
Tooltip: () => React.createElement('div', { 'data-testid': 'tooltip' }),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const { MetricChart } = await import('@/components/monitoring/tabs/MetricChart');
|
|
19
|
+
|
|
20
|
+
import { afterEach, describe, expect, test } from 'bun:test';
|
|
21
|
+
import { cleanup, render } from '@testing-library/react';
|
|
22
|
+
|
|
23
|
+
describe('MetricChart', () => {
|
|
24
|
+
afterEach(() => { cleanup(); });
|
|
25
|
+
|
|
26
|
+
test('shows collecting message when data has 0 points', () => {
|
|
27
|
+
const { getByText } = render(
|
|
28
|
+
<MetricChart data={[]} color="#3b82f6" title="CPU Usage" />
|
|
29
|
+
);
|
|
30
|
+
expect(getByText('Collecting data for CPU Usage...')).not.toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('shows collecting message when data has 1 point', () => {
|
|
34
|
+
const { getByText } = render(
|
|
35
|
+
<MetricChart data={[{ timestamp: Date.now(), value: 42 }]} color="#3b82f6" title="Memory" />
|
|
36
|
+
);
|
|
37
|
+
expect(getByText('Collecting data for Memory...')).not.toBeNull();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('does not render chart when data has fewer than 2 points', () => {
|
|
41
|
+
const { queryByTestId } = render(
|
|
42
|
+
<MetricChart data={[{ timestamp: Date.now(), value: 10 }]} color="#f00" title="Test" />
|
|
43
|
+
);
|
|
44
|
+
expect(queryByTestId('area-chart')).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('renders chart when data has 2+ points', () => {
|
|
48
|
+
const data = [
|
|
49
|
+
{ timestamp: 1000, value: 10 },
|
|
50
|
+
{ timestamp: 2000, value: 20 },
|
|
51
|
+
];
|
|
52
|
+
const { getByTestId, queryByText } = render(
|
|
53
|
+
<MetricChart data={data} color="#22c55e" title="Connections" />
|
|
54
|
+
);
|
|
55
|
+
expect(getByTestId('area-chart')).not.toBeNull();
|
|
56
|
+
expect(getByTestId('responsive-container')).not.toBeNull();
|
|
57
|
+
expect(queryByText(/Collecting data/)).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('passes data length to AreaChart', () => {
|
|
61
|
+
const data = [
|
|
62
|
+
{ timestamp: 1000, value: 10 },
|
|
63
|
+
{ timestamp: 2000, value: 20 },
|
|
64
|
+
{ timestamp: 3000, value: 30 },
|
|
65
|
+
];
|
|
66
|
+
const { getByTestId } = render(
|
|
67
|
+
<MetricChart data={data} color="#3b82f6" title="QPS" />
|
|
68
|
+
);
|
|
69
|
+
expect(getByTestId('area-chart').getAttribute('data-count')).toBe('3');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('passes color to Area stroke', () => {
|
|
73
|
+
const data = [
|
|
74
|
+
{ timestamp: 1000, value: 10 },
|
|
75
|
+
{ timestamp: 2000, value: 20 },
|
|
76
|
+
];
|
|
77
|
+
const { getByTestId } = render(
|
|
78
|
+
<MetricChart data={data} color="#ef4444" title="Errors" />
|
|
79
|
+
);
|
|
80
|
+
expect(getByTestId('area').getAttribute('data-color')).toBe('#ef4444');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('renders all chart sub-components', () => {
|
|
84
|
+
const data = [
|
|
85
|
+
{ timestamp: 1000, value: 10 },
|
|
86
|
+
{ timestamp: 2000, value: 20 },
|
|
87
|
+
];
|
|
88
|
+
const { getByTestId } = render(
|
|
89
|
+
<MetricChart data={data} color="#3b82f6" title="Latency" unit="ms" />
|
|
90
|
+
);
|
|
91
|
+
expect(getByTestId('x-axis')).not.toBeNull();
|
|
92
|
+
expect(getByTestId('y-axis')).not.toBeNull();
|
|
93
|
+
expect(getByTestId('tooltip')).not.toBeNull();
|
|
94
|
+
expect(getByTestId('area')).not.toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('uses title in collecting message', () => {
|
|
98
|
+
const { getByText } = render(
|
|
99
|
+
<MetricChart data={[]} color="#3b82f6" title="Custom Metric" />
|
|
100
|
+
);
|
|
101
|
+
expect(getByText('Collecting data for Custom Metric...')).not.toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('defaults unit to empty string', () => {
|
|
105
|
+
const { container } = render(
|
|
106
|
+
<MetricChart data={[]} color="#3b82f6" title="Test" />
|
|
107
|
+
);
|
|
108
|
+
// Should render without error (unit defaults to '')
|
|
109
|
+
expect(container).not.toBeNull();
|
|
110
|
+
});
|
|
111
|
+
});
|