mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 11:22:34 +00:00
Extends test/helpers/schema-diff.ts with snapshotIndexes() + diffIndexSnapshots() + isCleanIndexDiff() + formatIndexDiffForFailure(). Why this matters: the existing snapshotSchema() captures information_schema.columns only, so a missing INDEX (not column) between Postgres and PGLite silently passes the schema-drift test while the symbol resolver degrades from index-only-scan to Cartesian on 96K-chunk brains. The v0.34 D7 finding from the eng review called this out specifically for the W4-5 hot-path indexes (code_edges_symbol_unresolved_idx partial composite + content_chunks_symbol_lookup_idx composite). Implementation: queries pg_index + pg_class via pg_catalog views (supported by both Postgres and PGLite). Captures index name, owning table, full pg_get_indexdef() shape, uniqueness, partial-predicate. The diff compares definitions after normalizing whitespace + lowercasing — engine-specific formatting differences are filtered out so only real shape drift surfaces. Reused by future test/e2e/schema-drift.test.ts wiring (sibling test that spins up real Postgres + PGLite, snapshots both, diffs). test/helpers/schema-diff-indexes.test.ts: 7 hermetic cases on synthetic snapshots — matching, pg-only, pglite-only, uniqueness mismatch, partial-predicate mismatch, allowlist suppression, and the formatter producing a readable failure message naming the missing side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
3.6 KiB
TypeScript
86 lines
3.6 KiB
TypeScript
/**
|
|
* v0.34 D7 — snapshotIndexes helper unit tests.
|
|
*
|
|
* Pure-function tests for the index parity diff. The PG-vs-PGLite E2E
|
|
* wiring lives in `test/e2e/schema-drift.test.ts`; this file validates
|
|
* the diff logic in isolation against synthetic snapshots.
|
|
*/
|
|
import { describe, test, expect } from 'bun:test';
|
|
import {
|
|
diffIndexSnapshots,
|
|
isCleanIndexDiff,
|
|
formatIndexDiffForFailure,
|
|
type IndexSnapshot,
|
|
} from './schema-diff.ts';
|
|
|
|
function snap(entries: Array<{ name: string; table: string; columns: string; isUnique?: boolean; isPartial?: boolean }>): IndexSnapshot {
|
|
const m: IndexSnapshot = new Map();
|
|
for (const e of entries) {
|
|
m.set(e.name, {
|
|
indexName: e.name,
|
|
tableName: e.table,
|
|
columns: e.columns,
|
|
isUnique: e.isUnique ?? false,
|
|
isPartial: e.isPartial ?? false,
|
|
});
|
|
}
|
|
return m;
|
|
}
|
|
|
|
describe('snapshotIndexes diff', () => {
|
|
test('matching snapshots produce a clean diff', () => {
|
|
const pg = snap([{ name: 'idx_foo', table: 'foo', columns: 'CREATE INDEX idx_foo ON foo (id)' }]);
|
|
const pglite = snap([{ name: 'idx_foo', table: 'foo', columns: 'CREATE INDEX idx_foo ON foo (id)' }]);
|
|
const d = diffIndexSnapshots(pg, pglite);
|
|
expect(isCleanIndexDiff(d)).toBe(true);
|
|
});
|
|
|
|
test('pg-only index surfaces in pgOnly', () => {
|
|
const pg = snap([{ name: 'idx_only_pg', table: 'foo', columns: 'CREATE INDEX idx_only_pg ON foo (id)' }]);
|
|
const pglite = snap([]);
|
|
const d = diffIndexSnapshots(pg, pglite);
|
|
expect(d.pgOnly).toHaveLength(1);
|
|
expect(d.pgOnly[0]?.indexName).toBe('idx_only_pg');
|
|
expect(isCleanIndexDiff(d)).toBe(false);
|
|
});
|
|
|
|
test('pglite-only index surfaces in pgliteOnly', () => {
|
|
const pg = snap([]);
|
|
const pglite = snap([{ name: 'idx_pl_only', table: 'foo', columns: 'CREATE INDEX idx_pl_only ON foo (id)' }]);
|
|
const d = diffIndexSnapshots(pg, pglite);
|
|
expect(d.pgliteOnly).toHaveLength(1);
|
|
});
|
|
|
|
test('uniqueness mismatch surfaces in mismatched', () => {
|
|
const pg = snap([{ name: 'idx_u', table: 'foo', columns: 'CREATE UNIQUE INDEX idx_u ON foo (id)', isUnique: true }]);
|
|
const pglite = snap([{ name: 'idx_u', table: 'foo', columns: 'CREATE UNIQUE INDEX idx_u ON foo (id)', isUnique: false }]);
|
|
const d = diffIndexSnapshots(pg, pglite);
|
|
expect(d.mismatched).toHaveLength(1);
|
|
expect(d.mismatched[0]?.reason).toBe('uniqueness_mismatch');
|
|
});
|
|
|
|
test('partial-predicate mismatch surfaces in mismatched', () => {
|
|
const pg = snap([{ name: 'idx_p', table: 'foo', columns: 'CREATE INDEX idx_p ON foo (id) WHERE id IS NULL', isPartial: true }]);
|
|
const pglite = snap([{ name: 'idx_p', table: 'foo', columns: 'CREATE INDEX idx_p ON foo (id) WHERE id IS NULL', isPartial: false }]);
|
|
const d = diffIndexSnapshots(pg, pglite);
|
|
expect(d.mismatched).toHaveLength(1);
|
|
expect(d.mismatched[0]?.reason).toBe('partial_mismatch');
|
|
});
|
|
|
|
test('allowlist suppresses an index from the diff', () => {
|
|
const pg = snap([{ name: 'idx_only_pg', table: 'foo', columns: 'CREATE INDEX idx_only_pg ON foo (id)' }]);
|
|
const pglite = snap([]);
|
|
const d = diffIndexSnapshots(pg, pglite, { allowlist: ['idx_only_pg'] });
|
|
expect(isCleanIndexDiff(d)).toBe(true);
|
|
});
|
|
|
|
test('formatter produces a readable failure message', () => {
|
|
const pg = snap([{ name: 'idx_missing_pl', table: 'foo', columns: 'CREATE INDEX idx_missing_pl ON foo (id)' }]);
|
|
const pglite = snap([]);
|
|
const d = diffIndexSnapshots(pg, pglite);
|
|
const msg = formatIndexDiffForFailure(d);
|
|
expect(msg).toContain('idx_missing_pl');
|
|
expect(msg).toContain('MISSING in PGLite');
|
|
});
|
|
});
|