import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { resolveActiveModel } from '../../scanners/lib/active-model.mjs'; // B8b — resolveActiveModel reads the configured model so `--context-window auto` // can probe it. Source precedence mirrors Claude Code's own resolution: the shell // ANTHROPIC_MODEL override wins, otherwise the settings cascade (local > project > // user). Fully file/env-injectable so it is hermetic under the test HOME. async function withTempHome(fn) { const home = await mkdtemp(join(tmpdir(), 'config-audit-model-home-')); try { return await fn(home); } finally { await rm(home, { recursive: true, force: true }); } } async function withTempProject(fn) { const project = await mkdtemp(join(tmpdir(), 'config-audit-model-proj-')); try { return await fn(project); } finally { await rm(project, { recursive: true, force: true }); } } async function writeSettings(dir, file, obj) { await mkdir(dir, { recursive: true }); await writeFile(join(dir, file), JSON.stringify(obj)); } describe('resolveActiveModel — env override (ANTHROPIC_MODEL)', () => { it('returns the shell ANTHROPIC_MODEL when set, ignoring settings', async () => { await withTempHome(async (home) => { await writeSettings(join(home, '.claude'), 'settings.json', { model: 'claude-sonnet-4-6' }); const env = { HOME: home, ANTHROPIC_MODEL: 'claude-opus-4-8[1m]' }; assert.equal(await resolveActiveModel(null, { env }), 'claude-opus-4-8[1m]'); }); }); it('an empty ANTHROPIC_MODEL is treated as unset (falls through to the cascade)', async () => { await withTempHome(async (home) => { await writeSettings(join(home, '.claude'), 'settings.json', { model: 'claude-sonnet-4-6' }); const env = { HOME: home, ANTHROPIC_MODEL: ' ' }; assert.equal(await resolveActiveModel(null, { env }), 'claude-sonnet-4-6'); }); }); }); describe('resolveActiveModel — settings cascade (local > project > user)', () => { it('reads the model from user ~/.claude/settings.json', async () => { await withTempHome(async (home) => { await writeSettings(join(home, '.claude'), 'settings.json', { model: 'claude-opus-4-8' }); assert.equal(await resolveActiveModel(null, { env: { HOME: home } }), 'claude-opus-4-8'); }); }); it('project settings.json overrides user settings', async () => { await withTempHome(async (home) => { await writeSettings(join(home, '.claude'), 'settings.json', { model: 'claude-opus-4-8' }); await withTempProject(async (project) => { await writeSettings(join(project, '.claude'), 'settings.json', { model: 'claude-sonnet-4-6' }); assert.equal(await resolveActiveModel(project, { env: { HOME: home } }), 'claude-sonnet-4-6'); }); }); }); it('settings.local.json overrides project settings (local wins)', async () => { await withTempHome(async (home) => { await withTempProject(async (project) => { await writeSettings(join(project, '.claude'), 'settings.json', { model: 'claude-sonnet-4-6' }); await writeSettings(join(project, '.claude'), 'settings.local.json', { model: 'claude-opus-4-8[1m]' }); assert.equal(await resolveActiveModel(project, { env: { HOME: home } }), 'claude-opus-4-8[1m]'); }); }); }); }); describe('resolveActiveModel — nothing configured', () => { it('returns null on a clean HOME with no model anywhere (honest advisory fallback)', async () => { await withTempHome(async (home) => { assert.equal(await resolveActiveModel(null, { env: { HOME: home } }), null); }); }); it('ignores a non-string model field', async () => { await withTempHome(async (home) => { await writeSettings(join(home, '.claude'), 'settings.json', { model: 123 }); assert.equal(await resolveActiveModel(null, { env: { HOME: home } }), null); }); }); });