53 lines
4.2 KiB
TypeScript
53 lines
4.2 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { build } from 'esbuild'
|
|
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import { resolveSelectedModel } from '../src/shared/model-selection'
|
|
import type { CustomModelProfile, CustomModelInput } from '../src/shared/models'
|
|
|
|
const temp = await mkdtemp(join(tmpdir(), 'ycode-model-alignment-'))
|
|
const state = { path: join(temp, 'fresh') }
|
|
;(globalThis as any).__modelAlignment = state
|
|
let count = 0
|
|
try {
|
|
const outfile = join(temp, 'models.mjs')
|
|
await build({ entryPoints: ['src/main/models.ts'], outfile, bundle: true, platform: 'node', format: 'esm', logLevel: 'silent',
|
|
plugins: [{ name: 'offline-host', setup(b) {
|
|
b.onResolve({filter:/^electron$/},()=>({path:'host',namespace:'fixture'}))
|
|
b.onLoad({filter:/.*/,namespace:'fixture'},()=>({contents:`export const app={getPath:()=>globalThis.__modelAlignment.path}; export const safeStorage={isEncryptionAvailable:()=>false};`,loader:'js'}))
|
|
b.onResolve({filter:/^\.\/model-connection$/},()=>({path:'connection',namespace:'offline'}))
|
|
b.onResolve({filter:/^\.\/model-metadata$/},()=>({path:'metadata',namespace:'offline'}))
|
|
b.onLoad({filter:/.*/,namespace:'offline'},args=>({contents:args.path==='metadata'
|
|
? `export const modelMetadata=async()=>null;`
|
|
: `export const cachedConnection=()=>undefined,cachedGeneration=()=>undefined,discoverModelConnection=async()=>undefined,modelFetch=()=>{throw Error('external network forbidden in regression')},readModelJson=()=>{},rememberGeneration=()=>{};`,loader:'js'}))
|
|
}}] })
|
|
const reload=()=>import(`${pathToFileURL(outfile).href}?${++count}`) as Promise<typeof import('../src/main/models')>
|
|
let models=await reload()
|
|
assert.deepEqual(await models.listModels(),{models:[],selectedModelId:null},'fresh install must not synthesize deepseek')
|
|
const input=(id:string):CustomModelInput=>({id,name:id,model:id,baseUrl:'https://offline.invalid',providerId:'custom',authMode:'none',protocol:'anthropic'})
|
|
assert.equal((await models.saveModel(input('one'))).selectedModelId,'one')
|
|
assert.equal((await models.saveModel(input('two'))).selectedModelId,'two','new model becomes default')
|
|
assert.equal((await models.saveModel({...input('one'),previousId:'one'})).selectedModelId,'two','editing another model preserves selection')
|
|
const before=(await models.listModels()).models
|
|
assert.equal(resolveSelectedModel(before,'stale','two')?.id,'two')
|
|
assert.equal(resolveSelectedModel(before,'one','two')?.id,'one')
|
|
assert.equal(resolveSelectedModel(before,'stale','stale')?.id,'one')
|
|
assert.equal(resolveSelectedModel([],'one','two'),null)
|
|
assert.equal((await models.deleteModel('two')).selectedModelId,'one')
|
|
await models.deleteModel('one'); models=await reload()
|
|
assert.deepEqual(await models.listModels(),{models:[],selectedModelId:null})
|
|
assert.equal((await models.saveModel(input('three'))).selectedModelId,'three')
|
|
state.path=join(temp,'legacy'); await mkdir(state.path)
|
|
await writeFile(join(state.path,'config.json'),JSON.stringify({model:'user-model',baseUrl:'https://configured.invalid',temperature:0.2,maxToolRounds:4}))
|
|
models=await reload(); assert.equal((await models.listModels()).models[0]?.model,'user-model','explicit legacy config preserved')
|
|
state.path=join(temp,'preferences-only');await mkdir(state.path)
|
|
await writeFile(join(state.path,'config.json'),JSON.stringify({preventSleep:true}))
|
|
models=await reload();assert.deepEqual(await models.listModels(),{models:[],selectedModelId:null},'preferences are not a model')
|
|
state.path=join(temp,'serialized-defaults');await mkdir(state.path)
|
|
await writeFile(join(state.path,'config.json'),JSON.stringify({model:'deepseek-chat',baseUrl:'https://api.deepseek.com/v1',preventSleep:true}))
|
|
models=await reload();assert.deepEqual(await models.listModels(),{models:[],selectedModelId:null},'old preference writes of defaults are not real configuration')
|
|
console.log('PASS model alignment: fresh/legacy migration, new/edit/default/delete/reload, valid selection (offline host fixtures)')
|
|
} finally { delete (globalThis as any).__modelAlignment; await rm(temp,{recursive:true,force:true}) }
|