import { describe, test, expect } from 'bun:test'; describe('doctor command', () => { test('doctor module exports runDoctor', async () => { const { runDoctor } = await import('../src/commands/doctor.ts'); expect(typeof runDoctor).toBe('function'); }); test('LATEST_VERSION is importable from migrate', async () => { const { LATEST_VERSION } = await import('../src/core/migrate.ts'); expect(typeof LATEST_VERSION).toBe('number'); }); test('CLI registers doctor command', async () => { const result = Bun.spawnSync({ cmd: ['bun', 'run', 'src/cli.ts', '--help'], cwd: import.meta.dir + '/..', }); const stdout = new TextDecoder().decode(result.stdout); expect(stdout).toContain('doctor'); expect(stdout).toContain('--fast'); }); test('Check interface supports issues array', async () => { const { Check } = await import('../src/commands/doctor.ts'); // The Check type allows an optional issues array for resolver findings const check: import('../src/commands/doctor.ts').Check = { name: 'resolver_health', status: 'warn', message: '2 issues', issues: [{ type: 'unreachable', skill: 'test-skill', action: 'Add trigger row' }], }; expect(check.issues).toHaveLength(1); expect(check.issues![0].action).toContain('trigger'); }); test('runDoctor accepts null engine for filesystem-only mode', async () => { const { runDoctor } = await import('../src/commands/doctor.ts'); // runDoctor should accept null engine — it runs filesystem checks only. // Signature is (engine, args, dbSource?) — third param is optional and // used by --fast to distinguish "no config" from "user skipped DB check". // Function.length counts required params only (JS ignores ?-marked). expect(runDoctor.length).toBeGreaterThanOrEqual(2); expect(runDoctor.length).toBeLessThanOrEqual(3); }); // Bug 7 — --fast should differentiate "no config anywhere" from "user // chose --fast with GBRAIN_DATABASE_URL / config-file URL present". test('getDbUrlSource reflects GBRAIN_DATABASE_URL env var', async () => { const { getDbUrlSource } = await import('../src/core/config.ts'); const orig = process.env.GBRAIN_DATABASE_URL; const origAlt = process.env.DATABASE_URL; try { process.env.GBRAIN_DATABASE_URL = 'postgresql://test@localhost/x'; expect(getDbUrlSource()).toBe('env:GBRAIN_DATABASE_URL'); delete process.env.GBRAIN_DATABASE_URL; process.env.DATABASE_URL = 'postgresql://test@localhost/x'; expect(getDbUrlSource()).toBe('env:DATABASE_URL'); } finally { if (orig === undefined) delete process.env.GBRAIN_DATABASE_URL; else process.env.GBRAIN_DATABASE_URL = orig; if (origAlt === undefined) delete process.env.DATABASE_URL; else process.env.DATABASE_URL = origAlt; } }); test('doctor --fast emits source-specific message when URL present', async () => { const source = await Bun.file(new URL('../src/commands/doctor.ts', import.meta.url)).text(); // The source-aware message must reference the variable name so users // know where their URL is coming from. expect(source).toContain('Skipping DB checks (--fast mode, URL present from'); // The null-source fallback must still mention both config + env paths. expect(source).toContain('GBRAIN_DATABASE_URL'); }); // v0.12.2 reliability wave — doctor detects JSONB double-encode + truncated // bodies and points users at the standalone `gbrain repair-jsonb` command. // Detection only; repair lives in src/commands/repair-jsonb.ts. test('doctor source contains jsonb_integrity and markdown_body_completeness checks', async () => { const source = await Bun.file(new URL('../src/commands/doctor.ts', import.meta.url)).text(); expect(source).toContain('jsonb_integrity'); expect(source).toContain('markdown_body_completeness'); expect(source).toContain('gbrain repair-jsonb'); }); test('jsonb_integrity check covers the four JSONB sites fixed in v0.12.1', async () => { const source = await Bun.file(new URL('../src/commands/doctor.ts', import.meta.url)).text(); expect(source).toMatch(/table:\s*'pages'.*col:\s*'frontmatter'/); expect(source).toMatch(/table:\s*'raw_data'.*col:\s*'data'/); expect(source).toMatch(/table:\s*'ingest_log'.*col:\s*'pages_updated'/); expect(source).toMatch(/table:\s*'files'.*col:\s*'metadata'/); }); });