@aurabase/aurabase-js 0.3.0
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/.changeset/config.json +11 -0
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/package.json +90 -0
- package/packages/ai/LICENSE +21 -0
- package/packages/ai/README.md +106 -0
- package/packages/ai/dist/index.cjs +229 -0
- package/packages/ai/dist/index.d.cts +139 -0
- package/packages/ai/dist/index.d.ts +139 -0
- package/packages/ai/dist/index.js +226 -0
- package/packages/ai/node_modules/.bin/esbuild +19 -0
- package/packages/ai/node_modules/.bin/tsc +22 -0
- package/packages/ai/node_modules/.bin/tsserver +22 -0
- package/packages/ai/node_modules/.bin/tsup +22 -0
- package/packages/ai/node_modules/.bin/tsup-node +22 -0
- package/packages/ai/node_modules/.bin/vite +22 -0
- package/packages/ai/node_modules/.bin/vitest +22 -0
- package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/ai/package.json +61 -0
- package/packages/ai/src/AiService.ts +226 -0
- package/packages/ai/src/index.ts +18 -0
- package/packages/ai/src/sse.ts +141 -0
- package/packages/ai/src/types.ts +30 -0
- package/packages/ai/tests/chat-stream.test.ts +151 -0
- package/packages/ai/tests/rag-namespaces.test.ts +54 -0
- package/packages/ai/tests/sse.test.ts +69 -0
- package/packages/ai/tsconfig.json +5 -0
- package/packages/ai/tsup.config.ts +11 -0
- package/packages/aurabase/LICENSE +21 -0
- package/packages/aurabase/README.md +64 -0
- package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts +77 -0
- package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts +77 -0
- package/packages/aurabase/dist/index.cjs +237 -0
- package/packages/aurabase/dist/index.d.cts +46 -0
- package/packages/aurabase/dist/index.d.ts +46 -0
- package/packages/aurabase/dist/index.js +177 -0
- package/packages/aurabase/dist/ssr.cjs +224 -0
- package/packages/aurabase/dist/ssr.d.cts +65 -0
- package/packages/aurabase/dist/ssr.d.ts +65 -0
- package/packages/aurabase/dist/ssr.js +214 -0
- package/packages/aurabase/node_modules/.bin/esbuild +19 -0
- package/packages/aurabase/node_modules/.bin/tsc +22 -0
- package/packages/aurabase/node_modules/.bin/tsserver +22 -0
- package/packages/aurabase/node_modules/.bin/tsup +22 -0
- package/packages/aurabase/node_modules/.bin/tsup-node +22 -0
- package/packages/aurabase/node_modules/.bin/vite +22 -0
- package/packages/aurabase/node_modules/.bin/vitest +22 -0
- package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/aurabase/package.json +80 -0
- package/packages/aurabase/src/AuraClient.ts +237 -0
- package/packages/aurabase/src/index.ts +152 -0
- package/packages/aurabase/src/ssr.ts +156 -0
- package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +82 -0
- package/packages/aurabase/tests/ai.test.ts +111 -0
- package/packages/aurabase/tests/auth-admin.test.ts +515 -0
- package/packages/aurabase/tests/auth-e2e.test.ts +135 -0
- package/packages/aurabase/tests/auth-hardening.test.ts +206 -0
- package/packages/aurabase/tests/auth.test.ts +947 -0
- package/packages/aurabase/tests/database.test.ts +230 -0
- package/packages/aurabase/tests/db-types-e2e.test.ts +217 -0
- package/packages/aurabase/tests/dispose.test.ts +23 -0
- package/packages/aurabase/tests/foreign-token.e2e.test.ts +129 -0
- package/packages/aurabase/tests/functions.test.ts +107 -0
- package/packages/aurabase/tests/integration.test.ts +413 -0
- package/packages/aurabase/tests/live-production-validation.js +241 -0
- package/packages/aurabase/tests/live-sdk-production-validation.js +293 -0
- package/packages/aurabase/tests/mocks.ts +139 -0
- package/packages/aurabase/tests/notifications.test.ts +314 -0
- package/packages/aurabase/tests/observability-backup.test.ts +186 -0
- package/packages/aurabase/tests/observability-hook.test.ts +130 -0
- package/packages/aurabase/tests/production-robustness.test.ts +268 -0
- package/packages/aurabase/tests/public-api-surface.test.ts +53 -0
- package/packages/aurabase/tests/query-builder.test.ts +329 -0
- package/packages/aurabase/tests/readme-smoke-e2e.test.ts +231 -0
- package/packages/aurabase/tests/realtime-filter-e2e.test.ts +344 -0
- package/packages/aurabase/tests/refresh-locks.test.ts +210 -0
- package/packages/aurabase/tests/retry-option.test.ts +17 -0
- package/packages/aurabase/tests/rpc-e2e.test.ts +254 -0
- package/packages/aurabase/tests/rs256-local-e2e.test.ts +114 -0
- package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +235 -0
- package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +124 -0
- package/packages/aurabase/tests/service-role-guard.test.ts +99 -0
- package/packages/aurabase/tests/ssr.test.ts +91 -0
- package/packages/aurabase/tests/storage-backend-e2e.test.ts +481 -0
- package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +104 -0
- package/packages/aurabase/tests/storage-transform-e2e.test.ts +684 -0
- package/packages/aurabase/tests/storage.test.ts +55 -0
- package/packages/aurabase/tests/types/client-generic.test-d.ts +79 -0
- package/packages/aurabase/tests/types/docs-snippets.test-d.ts +571 -0
- package/packages/aurabase/tests/types/error-code.test-d.ts +53 -0
- package/packages/aurabase/tsconfig.json +5 -0
- package/packages/aurabase/tsconfig.test-d.json +5 -0
- package/packages/aurabase/tsup.config.ts +11 -0
- package/packages/aurabase/vitest.config.ts +16 -0
- package/packages/aurabase/vitest.e2e.config.ts +9 -0
- package/packages/auth/LICENSE +21 -0
- package/packages/auth/README.md +215 -0
- package/packages/auth/dist/index.cjs +1175 -0
- package/packages/auth/dist/index.d.cts +397 -0
- package/packages/auth/dist/index.d.ts +397 -0
- package/packages/auth/dist/index.js +1170 -0
- package/packages/auth/node_modules/.bin/esbuild +19 -0
- package/packages/auth/node_modules/.bin/tsc +22 -0
- package/packages/auth/node_modules/.bin/tsserver +22 -0
- package/packages/auth/node_modules/.bin/tsup +22 -0
- package/packages/auth/node_modules/.bin/tsup-node +22 -0
- package/packages/auth/node_modules/.bin/vite +22 -0
- package/packages/auth/node_modules/.bin/vitest +22 -0
- package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/auth/package.json +61 -0
- package/packages/auth/src/AuthAdminService.ts +149 -0
- package/packages/auth/src/AuthService.ts +1166 -0
- package/packages/auth/src/SessionManager.ts +92 -0
- package/packages/auth/src/index.ts +17 -0
- package/packages/auth/src/jwks.ts +337 -0
- package/packages/auth/src/pkce.ts +27 -0
- package/packages/auth/src/types.ts +89 -0
- package/packages/auth/tests/auth-lifecycle.test.ts +149 -0
- package/packages/auth/tests/detect-session.test.ts +361 -0
- package/packages/auth/tests/getclaims.test.ts +780 -0
- package/packages/auth/tests/is-mfa-challenge.test.ts +27 -0
- package/packages/auth/tests/password-recovery.test.ts +124 -0
- package/packages/auth/tests/pkce.test.ts +192 -0
- package/packages/auth/tests/session-manager.test.ts +103 -0
- package/packages/auth/tests/set-session.test.ts +139 -0
- package/packages/auth/tests/verify-otp.test.ts +153 -0
- package/packages/auth/tsconfig.json +5 -0
- package/packages/auth/tsup.config.ts +11 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +132 -0
- package/packages/core/dist/index.cjs +695 -0
- package/packages/core/dist/index.d.cts +1137 -0
- package/packages/core/dist/index.d.ts +1137 -0
- package/packages/core/dist/index.js +673 -0
- package/packages/core/node_modules/.bin/esbuild +22 -0
- package/packages/core/node_modules/.bin/tsc +22 -0
- package/packages/core/node_modules/.bin/tsserver +22 -0
- package/packages/core/node_modules/.bin/tsup +22 -0
- package/packages/core/node_modules/.bin/tsup-node +22 -0
- package/packages/core/node_modules/.bin/vite +22 -0
- package/packages/core/node_modules/.bin/vitest +22 -0
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/core/package.json +57 -0
- package/packages/core/src/AuraError.ts +89 -0
- package/packages/core/src/BaseService.ts +44 -0
- package/packages/core/src/client.ts +34 -0
- package/packages/core/src/cookie-utils.ts +42 -0
- package/packages/core/src/fetch.ts +435 -0
- package/packages/core/src/index.ts +22 -0
- package/packages/core/src/retry.ts +63 -0
- package/packages/core/src/serialize.ts +6 -0
- package/packages/core/src/stores/BaseAuthStore.ts +164 -0
- package/packages/core/src/stores/LocalAuthStore.ts +199 -0
- package/packages/core/src/types.ts +1042 -0
- package/packages/core/tests/auth-store.test.ts +300 -0
- package/packages/core/tests/error-code.test.ts +61 -0
- package/packages/core/tests/error.test.ts +24 -0
- package/packages/core/tests/fetch.test.ts +286 -0
- package/packages/core/tests/mocks.ts +139 -0
- package/packages/core/tests/observability.test.ts +86 -0
- package/packages/core/tests/refresh-hardening.test.ts +164 -0
- package/packages/core/tests/refresh-observability.test.ts +111 -0
- package/packages/core/tests/retry.test.ts +106 -0
- package/packages/core/tests/serialize.test.ts +12 -0
- package/packages/core/tests/timeout.test.ts +79 -0
- package/packages/core/tsconfig.json +5 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/db/LICENSE +21 -0
- package/packages/db/README.md +152 -0
- package/packages/db/VENDORED.md +93 -0
- package/packages/db/dist/index.cjs +544 -0
- package/packages/db/dist/index.d.cts +1111 -0
- package/packages/db/dist/index.d.ts +1111 -0
- package/packages/db/dist/index.js +541 -0
- package/packages/db/node_modules/.bin/esbuild +19 -0
- package/packages/db/node_modules/.bin/tsc +22 -0
- package/packages/db/node_modules/.bin/tsserver +22 -0
- package/packages/db/node_modules/.bin/tsup +22 -0
- package/packages/db/node_modules/.bin/tsup-node +22 -0
- package/packages/db/node_modules/.bin/vite +22 -0
- package/packages/db/node_modules/.bin/vitest +22 -0
- package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/db/package.json +62 -0
- package/packages/db/src/DatabaseService.ts +170 -0
- package/packages/db/src/QueryBuilder.ts +830 -0
- package/packages/db/src/index.ts +2 -0
- package/packages/db/src/pgrst-types/common/common.ts +86 -0
- package/packages/db/src/pgrst-types/common/rpc.ts +151 -0
- package/packages/db/src/pgrst-types/feature-flags.ts +20 -0
- package/packages/db/src/pgrst-types/types.ts +134 -0
- package/packages/db/src/select-query-parser/parser.ts +479 -0
- package/packages/db/src/select-query-parser/result.ts +553 -0
- package/packages/db/src/select-query-parser/types.ts +132 -0
- package/packages/db/src/select-query-parser/utils.ts +707 -0
- package/packages/db/tests/filters.test.ts +81 -0
- package/packages/db/tests/mutations-filtered.e2e.test.ts +132 -0
- package/packages/db/tests/mutations-filtered.test.ts +188 -0
- package/packages/db/tests/order.test.ts +45 -0
- package/packages/db/tests/rpc.test.ts +41 -0
- package/packages/db/tests/scale/build-large-schema.ts +94 -0
- package/packages/db/tests/scale/scale-compile.test.ts +167 -0
- package/packages/db/tests/schema.test.ts +77 -0
- package/packages/db/tests/single-accept.e2e.test.ts +123 -0
- package/packages/db/tests/single.test.ts +211 -0
- package/packages/db/tests/types/filters.test-d.ts +52 -0
- package/packages/db/tests/types/fixtures.ts +40 -0
- package/packages/db/tests/types/from-views.test-d.ts +18 -0
- package/packages/db/tests/types/mutations.test-d.ts +23 -0
- package/packages/db/tests/types/rpc.test-d.ts +60 -0
- package/packages/db/tests/types/schema-shape.test-d.ts +8 -0
- package/packages/db/tests/types/select-inference.test-d.ts +38 -0
- package/packages/db/tests/vendored/fixtures.ts +83 -0
- package/packages/db/tests/vendored/get-result.test-d.ts +75 -0
- package/packages/db/tsconfig.json +5 -0
- package/packages/db/tsconfig.test-d.json +5 -0
- package/packages/db/tsup.config.ts +11 -0
- package/packages/db/vitest.config.ts +8 -0
- package/packages/functions/LICENSE +21 -0
- package/packages/functions/README.md +96 -0
- package/packages/functions/dist/index.cjs +173 -0
- package/packages/functions/dist/index.d.cts +81 -0
- package/packages/functions/dist/index.d.ts +81 -0
- package/packages/functions/dist/index.js +168 -0
- package/packages/functions/node_modules/.bin/esbuild +19 -0
- package/packages/functions/node_modules/.bin/tsc +22 -0
- package/packages/functions/node_modules/.bin/tsserver +22 -0
- package/packages/functions/node_modules/.bin/tsup +22 -0
- package/packages/functions/node_modules/.bin/tsup-node +22 -0
- package/packages/functions/node_modules/.bin/vite +22 -0
- package/packages/functions/node_modules/.bin/vitest +22 -0
- package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/functions/package.json +60 -0
- package/packages/functions/src/FunctionsService.ts +188 -0
- package/packages/functions/src/errors.ts +70 -0
- package/packages/functions/src/index.ts +6 -0
- package/packages/functions/tests/invoke.test.ts +97 -0
- package/packages/functions/tests/response-type-errors.test.ts +135 -0
- package/packages/functions/tsconfig.json +5 -0
- package/packages/functions/tsup.config.ts +11 -0
- package/packages/gen/LICENSE +21 -0
- package/packages/gen/README.md +84 -0
- package/packages/gen/dist/cli.cjs +334 -0
- package/packages/gen/dist/cli.d.cts +1 -0
- package/packages/gen/dist/cli.d.ts +1 -0
- package/packages/gen/dist/cli.js +332 -0
- package/packages/gen/dist/index.cjs +257 -0
- package/packages/gen/dist/index.d.cts +12 -0
- package/packages/gen/dist/index.d.ts +12 -0
- package/packages/gen/dist/index.js +255 -0
- package/packages/gen/node_modules/.bin/esbuild +19 -0
- package/packages/gen/node_modules/.bin/tsc +22 -0
- package/packages/gen/node_modules/.bin/tsserver +22 -0
- package/packages/gen/node_modules/.bin/tsup +22 -0
- package/packages/gen/node_modules/.bin/tsup-node +22 -0
- package/packages/gen/node_modules/.bin/vite +22 -0
- package/packages/gen/node_modules/.bin/vitest +22 -0
- package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/gen/package.json +66 -0
- package/packages/gen/src/cli.ts +67 -0
- package/packages/gen/src/generate.ts +0 -0
- package/packages/gen/src/index.ts +2 -0
- package/packages/gen/tests/compile.test.ts +213 -0
- package/packages/gen/tests/e2e.test.ts +130 -0
- package/packages/gen/tests/generate.test.ts +396 -0
- package/packages/gen/tsconfig.json +5 -0
- package/packages/gen/tsup.config.ts +11 -0
- package/packages/notifications/LICENSE +21 -0
- package/packages/notifications/README.md +134 -0
- package/packages/notifications/dist/index.cjs +128 -0
- package/packages/notifications/dist/index.d.cts +81 -0
- package/packages/notifications/dist/index.d.ts +81 -0
- package/packages/notifications/dist/index.js +125 -0
- package/packages/notifications/node_modules/.bin/esbuild +19 -0
- package/packages/notifications/node_modules/.bin/tsc +22 -0
- package/packages/notifications/node_modules/.bin/tsserver +22 -0
- package/packages/notifications/node_modules/.bin/tsup +22 -0
- package/packages/notifications/node_modules/.bin/tsup-node +22 -0
- package/packages/notifications/node_modules/.bin/vite +22 -0
- package/packages/notifications/node_modules/.bin/vitest +22 -0
- package/packages/notifications/package.json +60 -0
- package/packages/notifications/src/NotificationsService.ts +230 -0
- package/packages/notifications/src/index.ts +19 -0
- package/packages/notifications/tsconfig.json +5 -0
- package/packages/notifications/tsup.config.ts +11 -0
- package/packages/realtime/LICENSE +21 -0
- package/packages/realtime/README.md +183 -0
- package/packages/realtime/dist/index.cjs +977 -0
- package/packages/realtime/dist/index.d.cts +259 -0
- package/packages/realtime/dist/index.d.ts +259 -0
- package/packages/realtime/dist/index.js +973 -0
- package/packages/realtime/node_modules/.bin/esbuild +19 -0
- package/packages/realtime/node_modules/.bin/tsc +22 -0
- package/packages/realtime/node_modules/.bin/tsserver +22 -0
- package/packages/realtime/node_modules/.bin/tsup +22 -0
- package/packages/realtime/node_modules/.bin/tsup-node +22 -0
- package/packages/realtime/node_modules/.bin/vite +22 -0
- package/packages/realtime/node_modules/.bin/vitest +22 -0
- package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/realtime/package.json +60 -0
- package/packages/realtime/src/AuraChannel.ts +485 -0
- package/packages/realtime/src/HeartbeatManager.ts +73 -0
- package/packages/realtime/src/RealtimeService.ts +505 -0
- package/packages/realtime/src/columnFilter.ts +301 -0
- package/packages/realtime/src/index.ts +4 -0
- package/packages/realtime/tests/ack-routing.test.ts +81 -0
- package/packages/realtime/tests/column-filter.test.ts +144 -0
- package/packages/realtime/tests/dispatch.test.ts +191 -0
- package/packages/realtime/tests/heartbeat.test.ts +67 -0
- package/packages/realtime/tests/http-broadcast.test.ts +68 -0
- package/packages/realtime/tests/realtime-token.test.ts +43 -0
- package/packages/realtime/tests/reconnect-race.test.ts +149 -0
- package/packages/realtime/tests/ref-id.test.ts +34 -0
- package/packages/realtime/tests/set-auth.test.ts +70 -0
- package/packages/realtime/tsconfig.json +5 -0
- package/packages/realtime/tsup.config.ts +11 -0
- package/packages/storage/LICENSE +21 -0
- package/packages/storage/README.md +141 -0
- package/packages/storage/dist/index.cjs +333 -0
- package/packages/storage/dist/index.d.cts +185 -0
- package/packages/storage/dist/index.d.ts +185 -0
- package/packages/storage/dist/index.js +329 -0
- package/packages/storage/node_modules/.bin/esbuild +19 -0
- package/packages/storage/node_modules/.bin/tsc +22 -0
- package/packages/storage/node_modules/.bin/tsserver +22 -0
- package/packages/storage/node_modules/.bin/tsup +22 -0
- package/packages/storage/node_modules/.bin/tsup-node +22 -0
- package/packages/storage/node_modules/.bin/vite +22 -0
- package/packages/storage/node_modules/.bin/vitest +22 -0
- package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/storage/package.json +60 -0
- package/packages/storage/src/DownloadBuilder.ts +60 -0
- package/packages/storage/src/StorageService.ts +323 -0
- package/packages/storage/src/index.ts +2 -0
- package/packages/storage/src/path.ts +35 -0
- package/packages/storage/src/transform.ts +15 -0
- package/packages/storage/tests/download.test.ts +64 -0
- package/packages/storage/tests/parity.test.ts +154 -0
- package/packages/storage/tests/path.test.ts +87 -0
- package/packages/storage/tests/transform.test.ts +79 -0
- package/packages/storage/tsconfig.json +5 -0
- package/packages/storage/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +24 -0
- package/tsconfig.base.json +15 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
|
|
2
|
+
import { createClient } from '../src/index'
|
|
3
|
+
|
|
4
|
+
const GATEWAY_URL = 'http://localhost:8080'
|
|
5
|
+
const MGMT_URL = 'http://localhost:8090'
|
|
6
|
+
|
|
7
|
+
describe('Tests de robustesse de production E2E (Multi-tenant, Transactions, Injection, Concurrence)', () => {
|
|
8
|
+
let devToken: string
|
|
9
|
+
let projectId: string
|
|
10
|
+
let anonKey: string
|
|
11
|
+
let serviceKey: string
|
|
12
|
+
|
|
13
|
+
const runId = Math.random().toString(36).substring(2, 10)
|
|
14
|
+
const devEmail = `robustness-dev-${runId}@aurabase.dev`
|
|
15
|
+
const devPassword = 'RobustPasswordE2E123!'
|
|
16
|
+
const projectName = `robustness-proj-${runId}`
|
|
17
|
+
|
|
18
|
+
const endUserEmail = `enduser-${runId}@example.com`
|
|
19
|
+
const endUserPassword = 'EndUserPassword123!'
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
// 1. Enregistrement développeur
|
|
23
|
+
const regRes = await fetch(`${MGMT_URL}/v1/control/accounts/register`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/json' },
|
|
26
|
+
body: JSON.stringify({ email: devEmail, password: devPassword }),
|
|
27
|
+
})
|
|
28
|
+
expect(regRes.status).toBe(200)
|
|
29
|
+
const regData = await regRes.json()
|
|
30
|
+
devToken = regData.data.access_token
|
|
31
|
+
|
|
32
|
+
// 2. Création organisation
|
|
33
|
+
const orgRes = await fetch(`${MGMT_URL}/v1/control/orgs`, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: {
|
|
36
|
+
'Authorization': `Bearer ${devToken}`,
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({ name: 'Robustness Org', slug: `robust-org-${runId}` }),
|
|
40
|
+
})
|
|
41
|
+
expect(orgRes.status).toBeLessThan(300)
|
|
42
|
+
const orgData = await orgRes.json()
|
|
43
|
+
const orgId = orgData.id
|
|
44
|
+
|
|
45
|
+
// 3. Création projet
|
|
46
|
+
const projRes = await fetch(`${MGMT_URL}/v1/control/orgs/${orgId}/projects`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: {
|
|
49
|
+
'Authorization': `Bearer ${devToken}`,
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify({ name: projectName, engine: 'postgres', region: 'eu-west-1' }),
|
|
53
|
+
})
|
|
54
|
+
expect(projRes.status).toBe(202)
|
|
55
|
+
const projData = await projRes.json()
|
|
56
|
+
projectId = projData.id
|
|
57
|
+
|
|
58
|
+
// Attente du provisioning
|
|
59
|
+
let active = false
|
|
60
|
+
for (let i = 0; i < 50; i++) {
|
|
61
|
+
const statusRes = await fetch(`${MGMT_URL}/v1/control/projects/${projectId}`, {
|
|
62
|
+
headers: { 'Authorization': `Bearer ${devToken}` },
|
|
63
|
+
})
|
|
64
|
+
const statusData = await statusRes.json()
|
|
65
|
+
if (statusData && statusData.status === 'active') {
|
|
66
|
+
active = true
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
70
|
+
}
|
|
71
|
+
expect(active).toBe(true)
|
|
72
|
+
|
|
73
|
+
// Récupération clés API (avec retry)
|
|
74
|
+
let keysRes: Response | null = null
|
|
75
|
+
for (let i = 0; i < 15; i++) {
|
|
76
|
+
keysRes = await fetch(`${MGMT_URL}/v1/control/projects/${projectId}/initial-keys`, {
|
|
77
|
+
headers: { 'Authorization': `Bearer ${devToken}` },
|
|
78
|
+
})
|
|
79
|
+
if (keysRes.status === 200) {
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
83
|
+
}
|
|
84
|
+
expect(keysRes).not.toBeNull()
|
|
85
|
+
expect(keysRes!.status).toBe(200)
|
|
86
|
+
const keysData = await keysRes!.json()
|
|
87
|
+
const keys = keysData.data || keysData
|
|
88
|
+
anonKey = keys.find((k: any) => k.scope === 'anon').key
|
|
89
|
+
serviceKey = keys.find((k: any) => k.scope === 'service_role').key
|
|
90
|
+
|
|
91
|
+
// 4. Création de la table 'tasks' avec RLS active
|
|
92
|
+
const tableDef = {
|
|
93
|
+
name: 'tasks',
|
|
94
|
+
columns: [
|
|
95
|
+
{
|
|
96
|
+
name: 'id',
|
|
97
|
+
col_type: 'uuid',
|
|
98
|
+
nullable: false,
|
|
99
|
+
unique: true,
|
|
100
|
+
default_val: 'gen_random_uuid()',
|
|
101
|
+
references: null
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'title',
|
|
105
|
+
col_type: 'text',
|
|
106
|
+
nullable: false,
|
|
107
|
+
unique: false,
|
|
108
|
+
default_val: null,
|
|
109
|
+
references: null
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'owner_id',
|
|
113
|
+
col_type: 'text',
|
|
114
|
+
nullable: false,
|
|
115
|
+
unique: false,
|
|
116
|
+
default_val: null,
|
|
117
|
+
references: null
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
primary_key: 'id',
|
|
121
|
+
indexes: [],
|
|
122
|
+
rls_enabled: true,
|
|
123
|
+
realtime_enabled: true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const tableRes = await fetch(`${MGMT_URL}/v1/db/${projectId}/schema/tables`, {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: {
|
|
129
|
+
'Authorization': `Bearer ${devToken}`,
|
|
130
|
+
'Content-Type': 'application/json',
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(tableDef),
|
|
133
|
+
})
|
|
134
|
+
expect(tableRes.status).toBe(200)
|
|
135
|
+
|
|
136
|
+
// Attendre l'indexation PostgREST
|
|
137
|
+
let ready = false
|
|
138
|
+
for (let i = 0; i < 30; i++) {
|
|
139
|
+
const probeRes = await fetch(`${GATEWAY_URL}/v1/db/${projectId}/tasks`, {
|
|
140
|
+
headers: { 'apikey': anonKey },
|
|
141
|
+
})
|
|
142
|
+
if (probeRes.status === 200) {
|
|
143
|
+
ready = true
|
|
144
|
+
break
|
|
145
|
+
}
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
147
|
+
}
|
|
148
|
+
expect(ready).toBe(true)
|
|
149
|
+
|
|
150
|
+
// Attendre 3 secondes que PostgREST stabilise son cache après les DDL de setup
|
|
151
|
+
await new Promise((resolve) => setTimeout(resolve, 3000))
|
|
152
|
+
}, 60000)
|
|
153
|
+
|
|
154
|
+
afterAll(async () => {
|
|
155
|
+
// Suppression propre
|
|
156
|
+
if (projectId && devToken) {
|
|
157
|
+
const delRes = await fetch(`${MGMT_URL}/v1/control/projects/${projectId}`, {
|
|
158
|
+
method: 'DELETE',
|
|
159
|
+
headers: { 'Authorization': `Bearer ${devToken}` },
|
|
160
|
+
})
|
|
161
|
+
expect(delRes.status).toBe(204)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('devrait exécuter des requêtes avec EXPLAIN ANALYZE', async () => {
|
|
166
|
+
const client = createClient(GATEWAY_URL, projectId, serviceKey, {
|
|
167
|
+
auth: { persistSession: false },
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const explainRes = await client.db.sql<any[]>('EXPLAIN ANALYZE SELECT * FROM tasks')
|
|
171
|
+
expect(explainRes.error).toBeNull()
|
|
172
|
+
expect(explainRes.data).toBeDefined()
|
|
173
|
+
expect(explainRes.data!.length).toBeGreaterThan(0)
|
|
174
|
+
|
|
175
|
+
// Vérifier la présence d'éléments typiques d'un plan d'exécution
|
|
176
|
+
const planText = JSON.stringify(explainRes.data)
|
|
177
|
+
expect(planText).toMatch(/Plan|Scan/i)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('devrait empêcher le contournement d\'isolation multi-tenant (Lecture inter-schéma)', async () => {
|
|
181
|
+
const client = createClient(GATEWAY_URL, projectId, serviceKey, {
|
|
182
|
+
auth: { persistSession: false },
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Tenter de lire un schéma d'un autre tenant inexistant ou existant (ex: project_autre_tenant)
|
|
186
|
+
// Devrait être rejeté avec une erreur 400 (BAD REQUEST) par Axum avant d'exécuter le SQL
|
|
187
|
+
const foreignSchemaRes = await client.db.sql('SELECT * FROM project_00000000_0000_0000_0000_000000000000.tasks')
|
|
188
|
+
expect(foreignSchemaRes.error).not.toBeNull()
|
|
189
|
+
expect(foreignSchemaRes.error?.code).toBe('BAD_REQUEST')
|
|
190
|
+
expect(foreignSchemaRes.error?.message).toContain('interdite')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('devrait garantir la sécurité face aux injections SQL', async () => {
|
|
194
|
+
const client = createClient(GATEWAY_URL, projectId, serviceKey, {
|
|
195
|
+
auth: { persistSession: false },
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
// Tentative d'injection SQL via filtre
|
|
199
|
+
const maliciosQueryRes = await client.db
|
|
200
|
+
.from('tasks')
|
|
201
|
+
.eq('title', "'; DROP TABLE tasks; --")
|
|
202
|
+
.select('*')
|
|
203
|
+
|
|
204
|
+
expect(maliciosQueryRes.error).toBeNull() // Sûr grâce à la paramétrisation PostgREST
|
|
205
|
+
|
|
206
|
+
// Vérifier que la table 'tasks' existe toujours
|
|
207
|
+
const probeRes = await client.db.from('tasks').select('*')
|
|
208
|
+
expect(probeRes.error).toBeNull()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('devrait exécuter des requêtes concurrentes saines (saturation de charge)', async () => {
|
|
212
|
+
const client = createClient(GATEWAY_URL, projectId, serviceKey, {
|
|
213
|
+
auth: { persistSession: false },
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const requests = Array.from({ length: 50 }).map((_, idx) =>
|
|
217
|
+
client.db.from('tasks').insert({
|
|
218
|
+
title: `Tâche concurrente ${idx}`,
|
|
219
|
+
owner_id: `owner_${idx}`,
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
const startTime = performance.now()
|
|
224
|
+
const results = await Promise.all(requests)
|
|
225
|
+
const duration = performance.now() - startTime
|
|
226
|
+
|
|
227
|
+
console.log(`[PERF] 50 requêtes concurrentes d'insertion exécutées en ${duration.toFixed(1)}ms`)
|
|
228
|
+
|
|
229
|
+
// Vérifier que toutes les requêtes ont réussi
|
|
230
|
+
for (const r of results) {
|
|
231
|
+
expect(r.error).toBeNull()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Vérifier que 50 lignes ont bien été insérées
|
|
235
|
+
const selectRes = await client.db.from('tasks').select('*')
|
|
236
|
+
expect(selectRes.data?.length).toBe(50)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('devrait gérer proprement les transactions SQL brutes (Commit & Rollback)', async () => {
|
|
240
|
+
const client = createClient(GATEWAY_URL, projectId, serviceKey, {
|
|
241
|
+
auth: { persistSession: false },
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// 1. Test du ROLLBACK : exécuter BEGIN, INSERT et ROLLBACK dans un seul bloc multi-statement
|
|
245
|
+
const txRollbackRes = await client.db.sql(
|
|
246
|
+
`BEGIN;
|
|
247
|
+
INSERT INTO tasks (title, owner_id) VALUES ('Task Tx Rollback', 'owner_tx');
|
|
248
|
+
ROLLBACK;`
|
|
249
|
+
)
|
|
250
|
+
expect(txRollbackRes.error).toBeNull()
|
|
251
|
+
|
|
252
|
+
// La ligne ne doit pas exister dans la base
|
|
253
|
+
const selectPostRollback = await client.db.from('tasks').eq('title', 'Task Tx Rollback')
|
|
254
|
+
expect(selectPostRollback.data?.length).toBe(0)
|
|
255
|
+
|
|
256
|
+
// 2. Test du COMMIT : exécuter BEGIN, INSERT et COMMIT dans un seul bloc
|
|
257
|
+
const txCommitRes = await client.db.sql(
|
|
258
|
+
`BEGIN;
|
|
259
|
+
INSERT INTO tasks (title, owner_id) VALUES ('Task Tx Commit', 'owner_tx');
|
|
260
|
+
COMMIT;`
|
|
261
|
+
)
|
|
262
|
+
expect(txCommitRes.error).toBeNull()
|
|
263
|
+
|
|
264
|
+
// La ligne doit exister
|
|
265
|
+
const selectPostCommit = await client.db.from('tasks').eq('title', 'Task Tx Commit')
|
|
266
|
+
expect(selectPostCommit.data?.length).toBe(1)
|
|
267
|
+
})
|
|
268
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import * as root from '../src/index'
|
|
3
|
+
import * as ssr from '../src/ssr'
|
|
4
|
+
|
|
5
|
+
describe('surface publique du meta-package aurabase', () => {
|
|
6
|
+
it('exporte exactement les symboles runtime attendus depuis la racine', () => {
|
|
7
|
+
const actual = Object.keys(root).sort()
|
|
8
|
+
const expected = [
|
|
9
|
+
'AiService',
|
|
10
|
+
'AuraChannel',
|
|
11
|
+
'AuraClient',
|
|
12
|
+
'AuraError',
|
|
13
|
+
'AuthAdminService',
|
|
14
|
+
'AuthService',
|
|
15
|
+
'BaseAuthStore',
|
|
16
|
+
'DatabaseService',
|
|
17
|
+
'FunctionsService',
|
|
18
|
+
'LocalAuthStore',
|
|
19
|
+
'NotificationsService',
|
|
20
|
+
'NotificationsAdminService',
|
|
21
|
+
'QueryBuilder',
|
|
22
|
+
'RealtimeService',
|
|
23
|
+
'StorageService',
|
|
24
|
+
'createAdminClient',
|
|
25
|
+
'createClient',
|
|
26
|
+
'isAuraError',
|
|
27
|
+
'isMfaChallenge',
|
|
28
|
+
].sort()
|
|
29
|
+
expect(actual).toEqual(expected)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('createClient fabrique un AuraClient fonctionnel', () => {
|
|
33
|
+
const client = root.createClient('http://localhost:8080', 'proj-1', 'aura_anon_x')
|
|
34
|
+
expect(client).toBeInstanceOf(root.AuraClient)
|
|
35
|
+
expect(client.baseUrl).toBe('http://localhost:8080')
|
|
36
|
+
expect(client.projectId).toBe('proj-1')
|
|
37
|
+
// sous-clients accessibles
|
|
38
|
+
expect(client.auth).toBeInstanceOf(root.AuthService)
|
|
39
|
+
expect(client.db).toBeInstanceOf(root.DatabaseService)
|
|
40
|
+
expect(client.storage).toBeInstanceOf(root.StorageService)
|
|
41
|
+
expect(client.functions).toBeInstanceOf(root.FunctionsService)
|
|
42
|
+
expect(client.ai).toBeInstanceOf(root.AiService)
|
|
43
|
+
expect(client.notifications).toBeInstanceOf(root.NotificationsService)
|
|
44
|
+
expect(client.realtime).toBeInstanceOf(root.RealtimeService)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('exporte les helpers SSR', () => {
|
|
48
|
+
const actual = Object.keys(ssr).sort()
|
|
49
|
+
expect(actual).toEqual(
|
|
50
|
+
['createBrowserClient', 'createServerClient', 'parseCookieHeader', 'serializeCookieHeader'].sort(),
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// QueryBuilder tests -- verify query string compilation for all operators
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
6
|
+
import { AuraClient } from '../src/AuraClient'
|
|
7
|
+
import { QueryBuilder } from '@aurabase/db'
|
|
8
|
+
import { FetchMock, fakeTokenPair } from './mocks'
|
|
9
|
+
|
|
10
|
+
const BASE = 'http://localhost:8080'
|
|
11
|
+
const PROJECT = 'test-project'
|
|
12
|
+
|
|
13
|
+
let fetchMock: FetchMock
|
|
14
|
+
let client: AuraClient
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
fetchMock = new FetchMock()
|
|
18
|
+
fetchMock.init()
|
|
19
|
+
client = new AuraClient(BASE, PROJECT, {
|
|
20
|
+
auth: { persistSession: false },
|
|
21
|
+
})
|
|
22
|
+
// Pre-populate auth so fetchWithAuth does not try to refresh
|
|
23
|
+
const tp = fakeTokenPair()
|
|
24
|
+
client.authStore.save(tp.access_token, tp.refresh_token, tp.user)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
fetchMock.restore()
|
|
29
|
+
fetchMock.clearMocks()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/** Helper: build a QueryBuilder for a table and return its query string. */
|
|
33
|
+
function qs(configure: (qb: QueryBuilder) => void): string {
|
|
34
|
+
const qb = new QueryBuilder(client, `/v1/db/${PROJECT}/posts`)
|
|
35
|
+
configure(qb)
|
|
36
|
+
return qb.buildQueryString()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Filter operators
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
describe('QueryBuilder filters', () => {
|
|
44
|
+
it('eq produces col=eq.val', () => {
|
|
45
|
+
const q = qs((qb) => qb.eq('status', 'active'))
|
|
46
|
+
expect(q).toContain('status=eq.active')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('neq produces col=neq.val', () => {
|
|
50
|
+
const q = qs((qb) => qb.neq('status', 'deleted'))
|
|
51
|
+
expect(q).toContain('status=neq.deleted')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('gt produces col=gt.val', () => {
|
|
55
|
+
const q = qs((qb) => qb.gt('age', 18))
|
|
56
|
+
expect(q).toContain('age=gt.18')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('gte produces col=gte.val', () => {
|
|
60
|
+
const q = qs((qb) => qb.gte('score', 90))
|
|
61
|
+
expect(q).toContain('score=gte.90')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('lt produces col=lt.val', () => {
|
|
65
|
+
const q = qs((qb) => qb.lt('price', 100))
|
|
66
|
+
expect(q).toContain('price=lt.100')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('lte produces col=lte.val', () => {
|
|
70
|
+
const q = qs((qb) => qb.lte('price', 50))
|
|
71
|
+
expect(q).toContain('price=lte.50')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('in produces col=in.(a,b,c)', () => {
|
|
75
|
+
const q = qs((qb) => qb.in('id', [1, 2, 3]))
|
|
76
|
+
expect(q).toContain('id=in.%281%2C2%2C3%29')
|
|
77
|
+
// Decoded: id=in.(1,2,3)
|
|
78
|
+
expect(decodeURIComponent(q)).toContain('id=in.(1,2,3)')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('like produces col=like.pattern', () => {
|
|
82
|
+
const q = qs((qb) => qb.like('name', '%john%'))
|
|
83
|
+
expect(decodeURIComponent(q)).toContain('name=like.%john%')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('ilike produces col=ilike.pattern', () => {
|
|
87
|
+
const q = qs((qb) => qb.ilike('name', '%JOHN%'))
|
|
88
|
+
expect(decodeURIComponent(q)).toContain('name=ilike.%JOHN%')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('is(col, null) produces col=is.null', () => {
|
|
92
|
+
const q = qs((qb) => qb.is('deleted_at', 'null'))
|
|
93
|
+
expect(q).toContain('deleted_at=is.null')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('is(col, not_null) produces col=is.not_null', () => {
|
|
97
|
+
const q = qs((qb) => qb.is('email', 'not_null'))
|
|
98
|
+
expect(q).toContain('email=is.not_null')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// OR groups
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe('QueryBuilder or()', () => {
|
|
107
|
+
it('produces or=(col1.eq.val1,col2.gt.val2)', () => {
|
|
108
|
+
const q = qs((qb) =>
|
|
109
|
+
qb.or([
|
|
110
|
+
{ column: 'status', op: 'eq', value: 'active' },
|
|
111
|
+
{ column: 'priority', op: 'gt', value: 5 },
|
|
112
|
+
]),
|
|
113
|
+
)
|
|
114
|
+
const decoded = decodeURIComponent(q)
|
|
115
|
+
expect(decoded).toContain('or=(status.eq.active,priority.gt.5)')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('supports in operator inside or group', () => {
|
|
119
|
+
const q = qs((qb) =>
|
|
120
|
+
qb.or([{ column: 'id', op: 'in', value: [1, 2] }]),
|
|
121
|
+
)
|
|
122
|
+
const decoded = decodeURIComponent(q)
|
|
123
|
+
expect(decoded).toContain('or=(id.in.(1,2))')
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Select
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
describe('QueryBuilder select()', () => {
|
|
132
|
+
it('select("id, title") compiles to select=id,title', () => {
|
|
133
|
+
const q = qs((qb) => qb.select('id, title'))
|
|
134
|
+
const decoded = decodeURIComponent(q)
|
|
135
|
+
expect(decoded).toContain('select=id,title')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('select(["id","title"]) works the same', () => {
|
|
139
|
+
const q = qs((qb) => qb.select(['id', 'title']))
|
|
140
|
+
const decoded = decodeURIComponent(q)
|
|
141
|
+
expect(decoded).toContain('select=id,title')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('select("*") omits the select param', () => {
|
|
145
|
+
const q = qs((qb) => qb.select('*'))
|
|
146
|
+
expect(q).not.toContain('select=')
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Order / Pagination
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
describe('QueryBuilder order / limit / offset', () => {
|
|
155
|
+
it('order("created_at", "desc") produces sort_by + sort_order', () => {
|
|
156
|
+
const q = qs((qb) => qb.order('created_at', 'desc'))
|
|
157
|
+
const decoded = decodeURIComponent(q)
|
|
158
|
+
expect(decoded).toContain('sort_by=created_at')
|
|
159
|
+
expect(decoded).toContain('sort_order=desc')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('limit(10).offset(20) produces limit=10&offset=20', () => {
|
|
163
|
+
const q = qs((qb) => qb.limit(10).offset(20))
|
|
164
|
+
const decoded = decodeURIComponent(q)
|
|
165
|
+
expect(decoded).toContain('limit=10')
|
|
166
|
+
expect(decoded).toContain('offset=20')
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Count
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
describe('QueryBuilder count()', () => {
|
|
175
|
+
it('count("exact") adds count=exact', () => {
|
|
176
|
+
const q = qs((qb) => qb.count('exact'))
|
|
177
|
+
expect(q).toContain('count=exact')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('count("estimated") adds count=estimated', () => {
|
|
181
|
+
const q = qs((qb) => qb.count('estimated'))
|
|
182
|
+
expect(q).toContain('count=estimated')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// single() and throwOnError()
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
describe('QueryBuilder single()', () => {
|
|
191
|
+
it('sets limit to 1 and unwraps the first element', async () => {
|
|
192
|
+
const rows = [{ id: '1', title: 'Hello' }]
|
|
193
|
+
fetchMock.on({
|
|
194
|
+
method: 'GET',
|
|
195
|
+
url: /\/posts\?/,
|
|
196
|
+
replyCode: 200,
|
|
197
|
+
replyBody: { data: rows },
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const res = await client.db.from('posts').eq('id', '1').single()
|
|
201
|
+
|
|
202
|
+
expect(res.data).toEqual({ id: '1', title: 'Hello' })
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('returns null when array is empty', async () => {
|
|
206
|
+
fetchMock.on({
|
|
207
|
+
method: 'GET',
|
|
208
|
+
url: /\/posts\?/,
|
|
209
|
+
replyCode: 200,
|
|
210
|
+
replyBody: { data: [] },
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const res = await client.db.from('posts').eq('id', 'nonexistent').single()
|
|
214
|
+
|
|
215
|
+
expect(res.data).toBeNull()
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('QueryBuilder throwOnError()', () => {
|
|
220
|
+
it('throws an error when response has error', async () => {
|
|
221
|
+
fetchMock.on({
|
|
222
|
+
method: 'GET',
|
|
223
|
+
url: /\/posts/,
|
|
224
|
+
replyCode: 500,
|
|
225
|
+
replyBody: { error: { code: 'internal', message: 'DB down' } },
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
await expect(
|
|
229
|
+
client.db.from('posts').throwOnError(),
|
|
230
|
+
).rejects.toThrow('DB down')
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Chained combinations
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
describe('QueryBuilder chained combinations', () => {
|
|
239
|
+
it('combines select + filters + order + pagination', () => {
|
|
240
|
+
const q = qs((qb) =>
|
|
241
|
+
qb
|
|
242
|
+
.select('id, title, created_at')
|
|
243
|
+
.eq('published', true)
|
|
244
|
+
.gt('views', 100)
|
|
245
|
+
.order('created_at', 'desc')
|
|
246
|
+
.limit(10)
|
|
247
|
+
.offset(0),
|
|
248
|
+
)
|
|
249
|
+
const decoded = decodeURIComponent(q)
|
|
250
|
+
expect(decoded).toContain('select=id,title,created_at')
|
|
251
|
+
expect(decoded).toContain('published=eq.true')
|
|
252
|
+
expect(decoded).toContain('views=gt.100')
|
|
253
|
+
expect(decoded).toContain('sort_by=created_at')
|
|
254
|
+
expect(decoded).toContain('sort_order=desc')
|
|
255
|
+
expect(decoded).toContain('limit=10')
|
|
256
|
+
expect(decoded).toContain('offset=0')
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('combines multiple filters with or group', () => {
|
|
260
|
+
const q = qs((qb) =>
|
|
261
|
+
qb
|
|
262
|
+
.eq('type', 'post')
|
|
263
|
+
.or([
|
|
264
|
+
{ column: 'status', op: 'eq', value: 'published' },
|
|
265
|
+
{ column: 'status', op: 'eq', value: 'draft' },
|
|
266
|
+
]),
|
|
267
|
+
)
|
|
268
|
+
const decoded = decodeURIComponent(q)
|
|
269
|
+
expect(decoded).toContain('type=eq.post')
|
|
270
|
+
expect(decoded).toContain('or=(status.eq.published,status.eq.draft)')
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// Resource Embedding (PostgREST parity)
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
describe('QueryBuilder resource embedding', () => {
|
|
279
|
+
it('select with embed passes raw string to ?select=', () => {
|
|
280
|
+
const q = qs((qb) => qb.select('*,customer:customers(name,email)'))
|
|
281
|
+
const decoded = decodeURIComponent(q)
|
|
282
|
+
expect(decoded).toContain('select=*,customer:customers(name,email)')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('select with one-to-many embed', () => {
|
|
286
|
+
const q = qs((qb) => qb.select('last_name,films(title,year)'))
|
|
287
|
+
const decoded = decodeURIComponent(q)
|
|
288
|
+
expect(decoded).toContain('select=last_name,films(title,year)')
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('select with FK hint', () => {
|
|
292
|
+
const q = qs((qb) =>
|
|
293
|
+
qb.select('*,billing:addresses!billing_address_id(name),shipping:addresses!shipping_address_id(name)'),
|
|
294
|
+
)
|
|
295
|
+
const decoded = decodeURIComponent(q)
|
|
296
|
+
expect(decoded).toContain('billing:addresses!billing_address_id(name)')
|
|
297
|
+
expect(decoded).toContain('shipping:addresses!shipping_address_id(name)')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('select with nested embedding', () => {
|
|
301
|
+
const q = qs((qb) => qb.select('first_name,roles(character,films(title))'))
|
|
302
|
+
const decoded = decodeURIComponent(q)
|
|
303
|
+
expect(decoded).toContain('select=first_name,roles(character,films(title))')
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('select with embed + filters + pagination', () => {
|
|
307
|
+
const q = qs((qb) =>
|
|
308
|
+
qb
|
|
309
|
+
.select('*,customer:customers(name)')
|
|
310
|
+
.eq('status', 'active')
|
|
311
|
+
.limit(10),
|
|
312
|
+
)
|
|
313
|
+
const decoded = decodeURIComponent(q)
|
|
314
|
+
expect(decoded).toContain('select=*,customer:customers(name)')
|
|
315
|
+
expect(decoded).toContain('status=eq.active')
|
|
316
|
+
expect(decoded).toContain('limit=10')
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('select star returns no select param', () => {
|
|
320
|
+
const q = qs((qb) => qb.select('*'))
|
|
321
|
+
expect(q).toBe('') // no params = select all
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('select with array still joins with comma', () => {
|
|
325
|
+
const q = qs((qb) => qb.select(['id', 'name', 'email']))
|
|
326
|
+
const decoded = decodeURIComponent(q)
|
|
327
|
+
expect(decoded).toContain('select=id,name,email')
|
|
328
|
+
})
|
|
329
|
+
})
|