// tests/lib/profile-resolver.test.mjs // v5.1.1 SC5 — non-interference cases for resolvePhaseModel(). // Verifies the new highest-priority lookup step (brief.phase_signals[phase].model) // wins over --profile flag and VOYAGE_PROFILE env; falls through cleanly when // no brief signal is present. import { test } from 'node:test'; import { strict as assert } from 'node:assert'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { resolvePhaseModel } from '../../lib/profiles/resolver.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = join(__dirname, '..', '..'); const FIXTURE = (name) => join(REPO_ROOT, 'tests', 'fixtures', name); test('resolvePhaseModel — Case 1: brief signal wins over VOYAGE_PROFILE env', () => { // brief-effort-low.md pins all 4 phases to model: sonnet. // env says premium (would normally select opus). Brief must win. const r = resolvePhaseModel('research', FIXTURE('brief-effort-low.md'), [], { VOYAGE_PROFILE: 'premium' }); assert.equal(r.model, 'sonnet', `brief signal should beat env; got ${JSON.stringify(r)}`); assert.equal(r.source, 'brief-signal'); }); test('resolvePhaseModel — Case 2: brief signal wins over --profile flag', () => { // brief-effort-high.md pins all 4 phases to model: opus. // flag says economy (would normally select sonnet). Brief must win. const r = resolvePhaseModel('execute', FIXTURE('brief-effort-high.md'), ['--profile', 'economy'], {}); assert.equal(r.model, 'opus', `brief signal should beat flag; got ${JSON.stringify(r)}`); assert.equal(r.source, 'brief-signal'); }); test('resolvePhaseModel — Case 3: no phase_signals → fallthrough to --profile flag', () => { // brief-without-phase-signals fixture lacks phase_signals entirely. // --profile balanced is set. Should return balanced.phase_models.plan (= opus per yaml). const r = resolvePhaseModel('plan', FIXTURE('brief-without-phase-signals.md'), ['--profile', 'balanced'], {}); assert.equal(r.model, 'opus', `balanced.plan should be opus; got ${JSON.stringify(r)}`); assert.equal(r.source, 'flag'); }); test('resolvePhaseModel — Case 4: phase not in PHASE_SIGNAL_PHASES falls through gracefully', () => { // brief-effort-high.md has signals for the 4 supported phases. // Asking for 'continue' (not in PHASE_SIGNAL_PHASES) must fall through. // --profile premium is set, so continue resolves to premium.phase_models.continue (= opus). const r = resolvePhaseModel('continue', FIXTURE('brief-effort-high.md'), ['--profile', 'premium'], {}); assert.equal(r.model, 'opus', `premium.continue should be opus; got ${JSON.stringify(r)}`); assert.ok(r.source !== 'brief-signal', 'continue must not resolve via brief-signal'); }); test('resolvePhaseModel — Case 5 (defensive): missing brief file falls through cleanly', () => { // Non-existent path. Must not throw; must fall through to flag/env/default. const r = resolvePhaseModel('plan', '/nonexistent/brief.md', ['--profile', 'economy'], {}); assert.equal(r.model, 'sonnet', 'economy.plan should be sonnet on fallthrough'); assert.equal(r.source, 'flag'); }); test('resolvePhaseModel — Case 6 (defensive): null briefPath falls through to default', () => { // null briefPath, no flag, no env → default = premium. const r = resolvePhaseModel('plan', null, [], {}); assert.equal(r.model, 'opus', 'premium.plan default = opus'); assert.equal(r.source, 'default'); });