studio.integration.test.ts71 lines · main
| 1 | /** |
| 2 | * Studio data-plane fixes against REAL DoltGres — sprint plan S1.1–S1.4. |
| 3 | * |
| 4 | * Guards the Batch-A studio fixes by calling the actual studio functions |
| 5 | * against a live DoltGres project database: |
| 6 | * - S1.2 executeQuery runs without the removed SET LOCAL statements |
| 7 | * - S1.3 truncateTable wipes rows (no RESTART IDENTITY) and rejects CASCADE |
| 8 | * - S1.4 listIndexes works via information_schema.statistics (the |
| 9 | * pg_index/array_position join DoltGres rejected is gone) |
| 10 | * (S1.1's lower()+LIKE substring match is locked by the compat alarm.) |
| 11 | * |
| 12 | * Skips when BRIVEN_DATA_PLANE_URL is unset. |
| 13 | */ |
| 14 | import { afterAll, beforeAll, describe, expect, test } from 'bun:test'; |
| 15 | |
| 16 | import { ValidationError } from '@briven/shared'; |
| 17 | |
| 18 | import { |
| 19 | closeProjectDbPools, |
| 20 | dropProjectDatabase, |
| 21 | provisionProjectDatabase, |
| 22 | runInProjectDatabase, |
| 23 | } from '../db/data-plane.js'; |
| 24 | import { executeQuery, listIndexes, truncateTable } from './studio.js'; |
| 25 | |
| 26 | const HAS_DB = Boolean(process.env.BRIVEN_DATA_PLANE_URL); |
| 27 | const PROJECT_ID = `p_studio${Date.now().toString(36)}`; |
| 28 | const TABLE = 'widgets'; |
| 29 | |
| 30 | describe.skipIf(!HAS_DB)('studio against real DoltGres (S1.1–S1.4)', () => { |
| 31 | beforeAll(async () => { |
| 32 | await provisionProjectDatabase(PROJECT_ID); |
| 33 | await runInProjectDatabase(PROJECT_ID, async (tx) => { |
| 34 | await tx.unsafe('SET dolt_transaction_commit = 1'); |
| 35 | await tx.unsafe(`CREATE TABLE "${TABLE}" (id text PRIMARY KEY, email text, name text)`); |
| 36 | await tx.unsafe(`CREATE INDEX "${TABLE}_email_idx" ON "${TABLE}" (email)`); |
| 37 | await tx.unsafe(`CREATE UNIQUE INDEX "${TABLE}_name_ux" ON "${TABLE}" (name)`); |
| 38 | await tx.unsafe( |
| 39 | `INSERT INTO "${TABLE}" (id, email, name) VALUES ('1','A@x.com','Alice'),('2','b@x.com','Bob')`, |
| 40 | ); |
| 41 | }); |
| 42 | }); |
| 43 | |
| 44 | afterAll(async () => { |
| 45 | await dropProjectDatabase(PROJECT_ID).catch(() => {}); |
| 46 | await closeProjectDbPools().catch(() => {}); |
| 47 | }); |
| 48 | |
| 49 | test('S1.4 listIndexes returns primary + secondary + unique with columns', async () => { |
| 50 | const idx = await listIndexes(PROJECT_ID, TABLE); |
| 51 | expect(idx.find((i) => i.isPrimary)).toBeTruthy(); |
| 52 | const email = idx.find((i) => i.columns.includes('email') && !i.isPrimary); |
| 53 | expect(email).toBeTruthy(); |
| 54 | expect(email?.unique).toBe(false); |
| 55 | const name = idx.find((i) => i.columns.includes('name') && !i.isPrimary); |
| 56 | expect(name).toBeTruthy(); |
| 57 | expect(name?.unique).toBe(true); |
| 58 | }); |
| 59 | |
| 60 | test('S1.2 executeQuery runs without SET LOCAL', async () => { |
| 61 | const res = await executeQuery(PROJECT_ID, `SELECT id FROM "${TABLE}" ORDER BY id`); |
| 62 | expect(res.rows.length).toBe(2); |
| 63 | }); |
| 64 | |
| 65 | test('S1.3 truncateTable wipes rows; cascade is rejected', async () => { |
| 66 | await truncateTable(PROJECT_ID, TABLE); |
| 67 | const res = await executeQuery(PROJECT_ID, `SELECT count(*)::int AS n FROM "${TABLE}"`); |
| 68 | expect(Number((res.rows[0] as { n: number }).n)).toBe(0); |
| 69 | await expect(truncateTable(PROJECT_ID, TABLE, true)).rejects.toThrow(ValidationError); |
| 70 | }); |
| 71 | }); |