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 */
14import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
15
16import { ValidationError } from '@briven/shared';
17
18import {
19 closeProjectDbPools,
20 dropProjectDatabase,
21 provisionProjectDatabase,
22 runInProjectDatabase,
23} from '../db/data-plane.js';
24import { executeQuery, listIndexes, truncateTable } from './studio.js';
25
26const HAS_DB = Boolean(process.env.BRIVEN_DATA_PLANE_URL);
27const PROJECT_ID = `p_studio${Date.now().toString(36)}`;
28const TABLE = 'widgets';
29
30describe.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});