Files
gbrain/test/source-resolver-silent-fallback.test.ts
T
Garry TanandClaude Opus 4.7 807542b1a2 test: fill gaps surfaced by post-Phase-2 audit (resolver silent-fallback + PGLite ordering)
Two test gaps identified by the test-coverage audit after Phase 1 + 2 shipped:

1. src/core/source-resolver.ts — the migration to isValidSourceId for
   silent-fallback tiers (dotfile read, brain_default config) had no
   direct test. Pre-PR these used inline regex; post-PR they use the
   canonical isValidSourceId. Codex P1-F intent (silent fallback for
   invalid input on tiers 3+5, throw on tiers 1+2) deserved an explicit
   test.

   test/source-resolver-silent-fallback.test.ts (12 cases):
     - tier 3: valid dotfile honored; underscore/whitespace/uppercase
       silently falls through to next tier
     - tier 5: valid brain_default honored; underscore + 33+ char silently
       falls through
     - tier 1: valid explicit --source returns; underscore/whitespace
       THROWS (contract distinction)
     - tier 2: valid env GBRAIN_SOURCE returns; underscore THROWS

2. src/core/cycle.ts — the PGLite file+DB ordering invariant (codex r2
   P0-C + P0-D) was implemented in Phase 1 (T5) but had no test pinning
   the ordering / cleanup / per-source DB lock ID semantics.

   test/cycle-pglite-lock-ordering.test.ts (6 cases):
     - global file lock acquired during PGLite cycle
     - cycle for source A then B serializes (file lock held in turn)
     - DB-lock acquire failure releases file lock cleanly (no stranded state)
     - engine=null path still uses file lock
     - DB lock row uses per-source ID (gbrain-cycle:<source>) not legacy
     - consecutive cycles can re-acquire both locks (release-on-exit works)

Plus .context/PHASE_3_ASSESSMENT.md (gitignored) documenting why each
Phase 3 DRY refactor item from the original plan is deferred: each item
on closer inspection is either a premature abstraction or was explicitly
rejected by the original author (BaseCyclePhase header comment).

18 new tests; 0 fails. typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 07:59:14 -07:00

149 lines
5.7 KiB
TypeScript

/**
* v0.38 source-resolver silent-fallback tier tests (codex P1-F).
*
* The resolver has 6 tiers. Two of them — dotfile (tier 3) and
* brain_default (tier 5) — were migrated in this wave to use
* `isValidSourceId` instead of inline regex. The intent: an invalid
* dotfile content or invalid brain_default config silently falls
* through to the next tier, rather than throwing.
*
* This is distinct from tiers 1 (explicit --source) and 2 (env), which
* MUST throw on invalid input because the user explicitly named them.
* Per codex P1-F, the resolver needs BOTH validator shapes.
*/
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
import { resetPgliteState } from './helpers/reset-pglite.ts';
import { resolveSourceId } from '../src/core/source-resolver.ts';
import { mkdtempSync, writeFileSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
let engine: PGLiteEngine;
let cwd: string;
beforeAll(async () => {
engine = new PGLiteEngine();
await engine.connect({ database_url: '' });
await engine.initSchema();
}, 60_000);
afterAll(async () => {
await engine.disconnect();
});
beforeEach(async () => {
await resetPgliteState(engine);
cwd = mkdtempSync(join(tmpdir(), 'gbrain-resolver-'));
});
describe('source-resolver silent-fallback tiers (codex P1-F)', () => {
describe('tier 3 — .gbrain-source dotfile', () => {
test('valid dotfile content (registered source) is honored', async () => {
await engine.executeRaw(
`INSERT INTO sources (id, name, config) VALUES ('alpha', 'alpha', '{}'::jsonb)
ON CONFLICT (id) DO NOTHING`,
);
writeFileSync(join(cwd, '.gbrain-source'), 'alpha\n');
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('alpha');
});
test('underscore in dotfile content silently falls through (strict regex rejects)', async () => {
// The strict regex rejects underscores. Pre-PR the permissive regex
// accepted them. After: invalid content falls through to next tier.
writeFileSync(join(cwd, '.gbrain-source'), 'has_underscore\n');
// No other tier signal — falls through to tier 6 'default'
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('default');
});
test('whitespace-only dotfile content silently falls through', async () => {
writeFileSync(join(cwd, '.gbrain-source'), ' \n');
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('default');
});
test('uppercase in dotfile content silently falls through', async () => {
writeFileSync(join(cwd, '.gbrain-source'), 'DEFAULT\n');
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('default');
});
});
describe('tier 5 — brain_default config', () => {
test('valid brain_default (registered source) is honored', async () => {
await engine.executeRaw(
`INSERT INTO sources (id, name, config) VALUES ('beta', 'beta', '{}'::jsonb)
ON CONFLICT (id) DO NOTHING`,
);
await engine.setConfig('sources.default', 'beta');
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('beta');
});
test('underscore in brain_default config silently falls through', async () => {
await engine.setConfig('sources.default', 'has_underscore');
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('default');
});
test('33+ char brain_default silently falls through', async () => {
const tooLong = 'a' + 'b'.repeat(31) + 'c'; // 33 chars
await engine.setConfig('sources.default', tooLong);
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('default');
});
});
describe('tier 1 — explicit --source (throw-on-invalid contract)', () => {
test('valid explicit source returns it', async () => {
await engine.executeRaw(
`INSERT INTO sources (id, name, config) VALUES ('gamma', 'gamma', '{}'::jsonb)
ON CONFLICT (id) DO NOTHING`,
);
const resolved = await resolveSourceId(engine, 'gamma', cwd);
expect(resolved).toBe('gamma');
});
test('underscore in explicit source THROWS (tier-1 contract)', async () => {
await expect(resolveSourceId(engine, 'has_underscore', cwd)).rejects.toThrow(/Invalid --source/);
});
test('whitespace in explicit source THROWS', async () => {
await expect(resolveSourceId(engine, 'has space', cwd)).rejects.toThrow(/Invalid --source/);
});
});
describe('tier 2 — GBRAIN_SOURCE env (throw-on-invalid contract)', () => {
const origEnv = process.env.GBRAIN_SOURCE;
afterAll(() => {
if (origEnv === undefined) delete process.env.GBRAIN_SOURCE;
else process.env.GBRAIN_SOURCE = origEnv;
});
test('valid env value is honored', async () => {
await engine.executeRaw(
`INSERT INTO sources (id, name, config) VALUES ('delta', 'delta', '{}'::jsonb)
ON CONFLICT (id) DO NOTHING`,
);
process.env.GBRAIN_SOURCE = 'delta';
try {
const resolved = await resolveSourceId(engine, null, cwd);
expect(resolved).toBe('delta');
} finally {
delete process.env.GBRAIN_SOURCE;
}
});
test('underscore in GBRAIN_SOURCE THROWS (tier-2 contract)', async () => {
process.env.GBRAIN_SOURCE = 'has_underscore';
try {
await expect(resolveSourceId(engine, null, cwd)).rejects.toThrow(/GBRAIN_SOURCE/);
} finally {
delete process.env.GBRAIN_SOURCE;
}
});
});
});