Files
gbrain/test/multi-source-write-path.test.ts
triton6564685 e5d94f63d9 feat(v0.18.0 Step 5): thread source_id through write path
Closes the in-code Step 5 TODO at postgres-engine.ts:131 + pglite-engine.ts:127.

- types.ts: PageInput.source_id?: string
- {postgres,pglite}-engine.ts putPage: accept source_id, INSERT explicit col when set
- {postgres,pglite}-engine.ts getTags/addTag/removeTag/upsertChunks/deleteChunks/
  createVersion: optional sourceId param scopes slug->page_id lookup (avoids
  subquery uniqueness violations on multi-source same-slug)
- engine.ts interface: matching optional sourceId params
- import-file.ts importFromContent/importFromFile: opts.sourceId, source-aware
  idempotency check, threads through entire transaction
- import.ts runImport: opts.sourceId
- sync.ts: thread opts.sourceId through 3 importFile call sites + unconditional
  resolveSourceId with pre-v0.17 backward-compat safety net (drop literal
  'default' to undefined when no explicit/env signal)
- operations.ts put_page handler: resolveSourceId chain, source_id param schema

Tests:
- test/multi-source-write-path.test.ts (new): putPage explicit/implicit, ON
  CONFLICT upsert, cross-source same-slug isolation, importFromContent
  threading, content_hash idempotency source-aware
- test/sync-resolveSourceId-unconditional-regression.test.ts (new, CRITICAL
  REGRESSION): pre-v0.17 brain backwards-compat, dotfile/cwd-prefix branches
  fire, sync.ts safety-net rule

bun test: 2182 pass / 0 fail / 250 skip (E2E DATABASE_URL gated). Baseline
preserved.

Strategy: full fork-local (no upstream PR sent), per /plan-eng-review T1
outside-voice tension reconsidered post-impl. Engine-method source-aware
expansion was discovered mid-impl when cross-source same-slug tests hit
SQL state 21000 (subquery uniqueness violation) on slug-only methods.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:10:44 +08:00

233 lines
7.7 KiB
TypeScript

/**
* v0.18.0 Step 5 — multi-source write path tests.
*
* Verifies that source_id threads end-to-end through every write surface:
*
* PageInput.source_id → putPage() INSERT (engine direct)
* importFromContent({sourceId}) → putPage() (parse + transaction)
* importFromFile({sourceId}) → importFromContent
* runImport({sourceId}) → importFile loop
*
* Both PGLite (this file) and Postgres (parity in test/e2e/mechanical.test.ts
* when DATABASE_URL is set) must agree on the per-row source_id outcome.
*
* Step-2-through-Step-4 schema invariants (default seed, composite UNIQUE,
* source_id col exists) are already covered in multi-source-integration.test.ts;
* this file focuses purely on the WRITE-THROUGH semantics that Step 5 introduces.
*/
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
import { importFromContent, importFromFile } from '../src/core/import-file.ts';
let engine: PGLiteEngine;
let tmpRoot: string;
beforeAll(async () => {
engine = new PGLiteEngine();
await engine.connect({ type: 'pglite' } as never);
await engine.initSchema();
// Pre-seed the named sources we'll route writes to. The 'default' row is
// seeded by migration v16; the rest we add explicitly so resolveSourceId
// / explicit threading have valid FK targets.
await engine.executeRaw(
`INSERT INTO sources (id, name, config) VALUES
('memory-dashboard', 'memory-dashboard', '{"federated": true}'::jsonb),
('stock-dashboard', 'stock-dashboard', '{"federated": true}'::jsonb)
ON CONFLICT (id) DO NOTHING`,
);
tmpRoot = mkdtempSync(join(tmpdir(), 'gbrain-step5-'));
});
afterAll(async () => {
await engine.disconnect();
rmSync(tmpRoot, { recursive: true, force: true });
});
describe('Step 5 — engine.putPage threading', () => {
test('putPage with explicit source_id writes to that source', async () => {
await engine.putPage('write-path/explicit-md', {
type: 'note',
title: 'Explicit',
compiled_truth: 'goes to memory-dashboard',
source_id: 'memory-dashboard',
});
const rows = await engine.executeRaw<{ source_id: string }>(
`SELECT source_id FROM pages WHERE slug = 'write-path/explicit-md'`,
);
expect(rows.length).toBe(1);
expect(rows[0].source_id).toBe('memory-dashboard');
});
test('putPage without source_id falls back to schema DEFAULT default', async () => {
await engine.putPage('write-path/implicit-md', {
type: 'note',
title: 'Implicit',
compiled_truth: 'no source_id passed',
});
const rows = await engine.executeRaw<{ source_id: string }>(
`SELECT source_id FROM pages WHERE slug = 'write-path/implicit-md'`,
);
expect(rows.length).toBe(1);
expect(rows[0].source_id).toBe('default');
});
test('putPage twice on same (source, slug) upserts in place', async () => {
await engine.putPage('write-path/upsert-key', {
type: 'note',
title: 'First',
compiled_truth: 'v1',
source_id: 'memory-dashboard',
});
await engine.putPage('write-path/upsert-key', {
type: 'note',
title: 'Second',
compiled_truth: 'v2',
source_id: 'memory-dashboard',
});
const rows = await engine.executeRaw<{ title: string; compiled_truth: string }>(
`SELECT title, compiled_truth FROM pages
WHERE source_id = 'memory-dashboard' AND slug = 'write-path/upsert-key'`,
);
expect(rows.length).toBe(1);
expect(rows[0].title).toBe('Second');
expect(rows[0].compiled_truth).toBe('v2');
});
test('putPage with same slug across two sources keeps both rows distinct', async () => {
await engine.putPage('write-path/same-slug', {
type: 'note',
title: 'In MD',
compiled_truth: 'memory-dashboard side',
source_id: 'memory-dashboard',
});
await engine.putPage('write-path/same-slug', {
type: 'note',
title: 'In SD',
compiled_truth: 'stock-dashboard side',
source_id: 'stock-dashboard',
});
const rows = await engine.executeRaw<{ source_id: string; title: string }>(
`SELECT source_id, title FROM pages
WHERE slug = 'write-path/same-slug'
ORDER BY source_id`,
);
expect(rows.length).toBe(2);
expect(rows[0].source_id).toBe('memory-dashboard');
expect(rows[1].source_id).toBe('stock-dashboard');
});
});
describe('Step 5 — importFromContent threading', () => {
test('importFromContent({sourceId}) writes via the threaded source', async () => {
const md = `---
title: From Content
type: note
---
# From Content
Hello world.
`;
const result = await importFromContent(engine, 'write-path/from-content', md, {
noEmbed: true,
sourceId: 'memory-dashboard',
});
expect(result.status).toBe('imported');
const rows = await engine.executeRaw<{ source_id: string }>(
`SELECT source_id FROM pages WHERE slug = 'write-path/from-content'`,
);
expect(rows[0].source_id).toBe('memory-dashboard');
});
test('importFromContent without sourceId opt → DEFAULT default', async () => {
const md = `---
title: From Content Default
type: note
---
Default-targeted body.
`;
const result = await importFromContent(engine, 'write-path/from-content-default', md, {
noEmbed: true,
});
expect(result.status).toBe('imported');
const rows = await engine.executeRaw<{ source_id: string }>(
`SELECT source_id FROM pages WHERE slug = 'write-path/from-content-default'`,
);
expect(rows[0].source_id).toBe('default');
});
});
describe('Step 5 — importFromFile threading', () => {
test('importFromFile({sourceId}) reads disk + writes to source', async () => {
const repoDir = join(tmpRoot, 'repo-a');
mkdirSync(repoDir, { recursive: true });
const filePath = join(repoDir, 'write-path-from-file.md');
writeFileSync(
filePath,
`---
title: From File
type: note
---
On-disk content routed to stock-dashboard.
`,
);
const result = await importFromFile(engine, filePath, 'write-path/from-file', {
noEmbed: true,
sourceId: 'stock-dashboard',
});
expect(result.status).toBe('imported');
const rows = await engine.executeRaw<{ source_id: string }>(
`SELECT source_id FROM pages WHERE slug = 'write-path/from-file'`,
);
expect(rows[0].source_id).toBe('stock-dashboard');
});
});
describe('Step 5 — content_hash idempotency unaffected by source_id', () => {
test('rewriting identical content to same source returns skipped', async () => {
const md = `---
title: Idempotent
type: note
---
Stable body.
`;
const r1 = await importFromContent(engine, 'write-path/idempotent', md, {
noEmbed: true,
sourceId: 'memory-dashboard',
});
expect(r1.status).toBe('imported');
const r2 = await importFromContent(engine, 'write-path/idempotent', md, {
noEmbed: true,
sourceId: 'memory-dashboard',
});
expect(r2.status).toBe('skipped');
});
test('same slug in different source counts as a separate page (not skip)', async () => {
const md = `---
title: Cross-Source Slug
type: note
---
Same body, different source.
`;
const r1 = await importFromContent(engine, 'write-path/cross-slug', md, {
noEmbed: true,
sourceId: 'memory-dashboard',
});
expect(r1.status).toBe('imported');
const r2 = await importFromContent(engine, 'write-path/cross-slug', md, {
noEmbed: true,
sourceId: 'stock-dashboard',
});
// Different (source_id, slug) row → must be a fresh import, not a skip.
expect(r2.status).toBe('imported');
});
});