Closes the upstream Step 5 deferral noted in schema-embedded.ts:202-204
("ingest_log.source_id is NOT added yet — lands in v17 alongside the sync
rewrite (Step 5)"). Upstream's v17 only addressed pages.source_id; the
ingest_log half was deferred without ever shipping.
- migrate.ts: v25 ALTER TABLE adds source_id NOT NULL DEFAULT 'default'
REFERENCES sources(id) ON DELETE CASCADE + idx_ingest_log_source_id
- schema-embedded.ts: fresh-install schema mirrors the migration outcome
- types.ts: IngestLogInput.source_id?: string
- {postgres,pglite}-engine.ts logIngest: thread entry.source_id when set,
fall back to schema DEFAULT 'default' otherwise
- import.ts + sync.ts: pass opts.sourceId to logIngest call sites
Tests:
- test/ingest-log-source-id.test.ts (new): col schema, FK enforcement,
logIngest write-through both source-explicit and default-fallback paths
Strategy: fork-local commit, NOT sent upstream — kept separate from
Phase 1 to make it easy to drop if upstream eventually adds their own
ingest_log.source_id (would just be a rebase delete).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
/**
|
|
* v0.18.2.fork.1 — migration v25 ingest_log.source_id.
|
|
*
|
|
* Closes the upstream Step 5 deferral noted at schema-embedded.ts:202-204:
|
|
*
|
|
* "ingest_log.source_id is NOT added yet — lands in v17 alongside the
|
|
* sync rewrite (Step 5)"
|
|
*
|
|
* Verifies:
|
|
* - migration v25 adds the column with NOT NULL DEFAULT 'default'
|
|
* - existing rows backfill to 'default' (the schema seed exists)
|
|
* - new rows can be written with explicit source_id
|
|
* - logIngest signature accepts entry.source_id and threads it through
|
|
* - omitting source_id falls back to schema DEFAULT 'default'
|
|
*/
|
|
|
|
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
|
|
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
|
|
|
let engine: PGLiteEngine;
|
|
|
|
beforeAll(async () => {
|
|
engine = new PGLiteEngine();
|
|
await engine.connect({ type: 'pglite' } as never);
|
|
await engine.initSchema();
|
|
await engine.executeRaw(
|
|
`INSERT INTO sources (id, name, config) VALUES
|
|
('memory-dashboard', 'memory-dashboard', '{"federated": true}'::jsonb)
|
|
ON CONFLICT (id) DO NOTHING`,
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await engine.disconnect();
|
|
});
|
|
|
|
describe('v25 — ingest_log.source_id schema', () => {
|
|
test('source_id column exists with NOT NULL DEFAULT default', async () => {
|
|
const rows = await engine.executeRaw<{ column_default: string | null; is_nullable: string }>(
|
|
`SELECT column_default, is_nullable FROM information_schema.columns
|
|
WHERE table_name = 'ingest_log' AND column_name = 'source_id'`,
|
|
);
|
|
expect(rows.length).toBe(1);
|
|
expect(rows[0].is_nullable).toBe('NO');
|
|
expect(rows[0].column_default).toContain('default');
|
|
});
|
|
|
|
test('idx_ingest_log_source_id index exists', async () => {
|
|
const rows = await engine.executeRaw<{ indexname: string }>(
|
|
`SELECT indexname FROM pg_indexes WHERE indexname = 'idx_ingest_log_source_id'`,
|
|
);
|
|
expect(rows.length).toBe(1);
|
|
});
|
|
|
|
test('FK to sources(id) is enforced (insert with bogus source rejected)', async () => {
|
|
let threw = false;
|
|
try {
|
|
await engine.executeRaw(
|
|
`INSERT INTO ingest_log (source_id, source_type, source_ref, summary)
|
|
VALUES ('does-not-exist', 'directory', '/tmp', '')`,
|
|
);
|
|
} catch {
|
|
threw = true;
|
|
}
|
|
expect(threw).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('v25 — logIngest write-through', () => {
|
|
test('logIngest with source_id writes to that source', async () => {
|
|
await engine.logIngest({
|
|
source_type: 'directory',
|
|
source_ref: '/tmp/md',
|
|
pages_updated: ['a', 'b'],
|
|
summary: 'test ingest md',
|
|
source_id: 'memory-dashboard',
|
|
});
|
|
const rows = await engine.executeRaw<{ source_id: string }>(
|
|
`SELECT source_id FROM ingest_log WHERE source_ref = '/tmp/md'`,
|
|
);
|
|
expect(rows.length).toBe(1);
|
|
expect(rows[0].source_id).toBe('memory-dashboard');
|
|
});
|
|
|
|
test('logIngest without source_id falls back to schema DEFAULT default', async () => {
|
|
await engine.logIngest({
|
|
source_type: 'directory',
|
|
source_ref: '/tmp/legacy',
|
|
pages_updated: [],
|
|
summary: 'legacy single-source caller',
|
|
});
|
|
const rows = await engine.executeRaw<{ source_id: string }>(
|
|
`SELECT source_id FROM ingest_log WHERE source_ref = '/tmp/legacy'`,
|
|
);
|
|
expect(rows.length).toBe(1);
|
|
expect(rows[0].source_id).toBe('default');
|
|
});
|
|
});
|