full-loop.integration.test.ts85 lines · main
1/**
2 * Full create→write→read→live-update loop — the "smoke alarm" (sprint plan S0.3).
3 *
4 * Exercises the REAL data-plane helpers (not a re-implementation) against a
5 * live DoltGres, proving the exact path ISY hits:
6 *
7 * provision a project database
8 * → write a row with `SET dolt_transaction_commit = 1`
9 * → DOLT_HASHOF('HEAD') ADVANCES (this is what the realtime poller
10 * watches — so a "live update" would actually fire)
11 * → read the row back
12 * → drop the database (cleanup)
13 *
14 * If any link breaks on DoltGres, it fails HERE instead of in production.
15 *
16 * How to run (needs the local DoltGres container up on :5433):
17 * BRIVEN_DATA_PLANE_URL=postgres://postgres:password@127.0.0.1:5433/postgres \
18 * bun test src/db/full-loop.integration.test.ts
19 *
20 * Skips entirely when BRIVEN_DATA_PLANE_URL is unset (keeps the no-DB unit run green).
21 */
22import { afterAll, describe, expect, test } from 'bun:test';
23
24import {
25 closeProjectDbPools,
26 dropProjectDatabase,
27 provisionProjectDatabase,
28 runInProjectDatabase,
29} from './data-plane.js';
30
31const HAS_DB = Boolean(process.env.BRIVEN_DATA_PLANE_URL);
32
33// A throwaway project id, unique per run (Date.now is fine in a test file).
34const PROJECT_ID = `p_smoke${Date.now().toString(36)}`;
35
36describe.skipIf(!HAS_DB)('full create→write→read→live-update loop', () => {
37 afterAll(async () => {
38 // Always try to clean up the throwaway database + pools.
39 await dropProjectDatabase(PROJECT_ID).catch(() => {});
40 await closeProjectDbPools().catch(() => {});
41 });
42
43 test('provision → committed write advances HEAD → read back', async () => {
44 // 1. Provision the project's own DoltGres database (real helper).
45 const dbName = await provisionProjectDatabase(PROJECT_ID);
46 expect(dbName).toContain('smoke');
47
48 // 2. Baseline commit hash, read right before the measured write.
49 const baseline = await runInProjectDatabase(PROJECT_ID, async (tx) => {
50 const rows = await tx.unsafe(`SELECT DOLT_HASHOF('HEAD') AS h`);
51 return rows[0]?.h as string | null;
52 });
53
54 // 3. Create a table + insert a row in ONE committed Dolt transaction.
55 // `SET dolt_transaction_commit = 1` is what makes COMMIT a real Dolt
56 // commit — exactly how studio.ts / the runtime write.
57 await runInProjectDatabase(PROJECT_ID, async (tx) => {
58 await tx.unsafe('SET dolt_transaction_commit = 1');
59 await tx.unsafe(
60 `CREATE TABLE "notes" (id text PRIMARY KEY, title text NOT NULL, body text)`,
61 );
62 await tx.unsafe(`INSERT INTO "notes" (id, title, body) VALUES ($1,$2,$3)`, [
63 'n1',
64 'hello ISY',
65 'first row',
66 ]);
67 });
68
69 // 4. HEAD must have ADVANCED — this is the signal the realtime poller
70 // (poll-manager.ts: SELECT DOLT_HASHOF('HEAD')) turns into a live push.
71 const after = await runInProjectDatabase(PROJECT_ID, async (tx) => {
72 const rows = await tx.unsafe(`SELECT DOLT_HASHOF('HEAD') AS h`);
73 return rows[0]?.h as string | null;
74 });
75 expect(after).toBeTruthy();
76 expect(after).not.toBe(baseline);
77
78 // 5. Read the row back through the real per-project path.
79 const row = await runInProjectDatabase(PROJECT_ID, async (tx) => {
80 const rows = await tx.unsafe(`SELECT id, title, body FROM "notes" WHERE id = $1`, ['n1']);
81 return rows[0] as { id: string; title: string; body: string } | undefined;
82 });
83 expect(row).toEqual({ id: 'n1', title: 'hello ISY', body: 'first row' });
84 });
85});