import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; const modelDiagnosticsMocks = vi.hoisted(() => ({ config: {} as Record, writeOpenClawConfig: vi.fn(), testOpenClawDir: '/tmp/clawx-tests/model-diagnostics-openclaw', })); vi.mock('@electron/utils/channel-config', () => ({ readOpenClawConfig: vi.fn(async () => modelDiagnosticsMocks.config), writeOpenClawConfig: vi.fn(async (config: Record) => { modelDiagnosticsMocks.config = config; modelDiagnosticsMocks.writeOpenClawConfig(config); }), })); vi.mock('@electron/utils/paths', () => ({ getOpenClawConfigDir: () => modelDiagnosticsMocks.testOpenClawDir, })); vi.mock('@electron/utils/logger', () => ({ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), }, })); function authProfilesPath(homeDir: string): string { return join(homeDir, '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json'); } describe('Yinian model diagnostics', () => { const originalHome = process.env.HOME; const testHome = join(tmpdir(), 'clawx-tests', 'model-diagnostics-home'); beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); process.env.HOME = testHome; rmSync(testHome, { recursive: true, force: true }); rmSync(modelDiagnosticsMocks.testOpenClawDir, { recursive: true, force: true }); mkdirSync(join(testHome, '.openclaw', 'agents', 'main', 'agent'), { recursive: true }); mkdirSync(modelDiagnosticsMocks.testOpenClawDir, { recursive: true }); modelDiagnosticsMocks.config = { models: { providers: { minimax: { baseUrl: 'https://api.minimaxi.com/anthropic', api: 'anthropic-messages', timeoutSeconds: 30, models: [{ id: 'MiniMax-M2.7' }], }, }, pricing: { enabled: true, }, }, agents: { defaults: { model: { primary: 'minimax/MiniMax-M2.7', fallbacks: ['minimax/MiniMax-M2.5'], }, }, }, }; writeFileSync(authProfilesPath(testHome), JSON.stringify({ version: 1, profiles: { 'minimax-cn:default': { type: 'api_key', provider: 'minimax-cn', key: 'secret', }, }, }, null, 2)); }); afterEach(() => { process.env.HOME = originalHome; rmSync(testHome, { recursive: true, force: true }); rmSync(modelDiagnosticsMocks.testOpenClawDir, { recursive: true, force: true }); }); it('repairs runtime model defaults and normalizes MiniMax auth profile aliases', async () => { const { buildYinianModelConfigDiagnostics, ensureYinianModelRuntimeConfigured, } = await import('@electron/utils/model-diagnostics'); await ensureYinianModelRuntimeConfigured(); expect(modelDiagnosticsMocks.writeOpenClawConfig).toHaveBeenCalledWith(expect.objectContaining({ models: expect.objectContaining({ providers: expect.objectContaining({ minimax: expect.not.objectContaining({ timeoutSeconds: expect.anything() }), }), }), agents: expect.objectContaining({ defaults: expect.objectContaining({ heartbeat: expect.objectContaining({ every: '0m' }), }), }), })); const authStore = JSON.parse(readFileSync(authProfilesPath(testHome), 'utf8')) as { profiles: Record; order?: Record; lastGood?: Record; }; expect(authStore.profiles['minimax:default']).toMatchObject({ provider: 'minimax', key: 'secret', }); expect(authStore.order?.minimax).toContain('minimax:default'); expect(authStore.lastGood?.minimax).toBe('minimax:default'); const diagnostics = await buildYinianModelConfigDiagnostics(); expect(diagnostics.ok).toBe(true); expect(diagnostics.model.primary).toBe('minimax/MiniMax-M2.7'); expect(diagnostics.runtime.heartbeatDisabled).toBe(true); expect(diagnostics.checks.find((check) => check.id === 'auth-profile')?.status).toBe('ok'); }); });