дȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9
¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档 д Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£ Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£ Ñé֤£ºÁ½¸ö Go ģ¿éµIJâÊÔ/vet¡¢¹ؼüģ¿é race¡¢ǰ¶˲âÊÔ/Éú²ú¹¹½¨/Éó¼ơ¢ Go ¿ɴï©¶´ɨÃèÓë macOS/Linux ¿ÉÐÐģ¿齻²æ±àÒë¾ùͨ¹ý¡£ Windows ¸ôÀëÆô¶¯ͨ¹ý£»δÍê³ɵĵã»÷ðÑ̺ÍԭÉúƽ̨Ñé֤¼ûÉó²鱨¸档
This commit is contained in:
32
frontend/test/appState.test.js
Normal file
32
frontend/test/appState.test.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
test('real app state preserves the CLI port and rejects stale editor/full-form saves', async () => {
|
||||
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
||||
const adapter={id:'adapter-1',type:'openai',displayName:'Fixture',baseURL:'https://fixture.invalid',apiKey:'fixture-only',tooltipData:'fixture',modelID:'model',reasoningEffort:'high',openAIEndpoint:'/v1/responses'};
|
||||
let config={revision:'r1',backendListenAddr:'127.0.0.1:28090',proxyListenAddr:'127.0.0.1:28080',cliHttpsListenAddr:'127.0.0.1:39999',modelAdapters:[adapter],routing:{mode:'local'},homeMetrics:{persistentMode:true,refreshIntervalSeconds:60},tabServerBaseURL:'',tabServerToken:''};
|
||||
const writes=[];
|
||||
globalThis.__configReviewFixture={load:()=>structuredClone(config),save:payload=>{assert.equal(payload.revision,config.revision);writes.push(structuredClone(payload));config={...structuredClone(payload),revision:'r'+(writes.length+1)};}};
|
||||
const apiNames=['getAppVersion','getHomeMetricsSummary','getModelAdapterTestResults','getProxyState','listRemoteModels','openConfigWindow','loadUserConfig','openLogsDirectory','openModelConfig','openModelEditor','saveUserConfig','startProxyService','stopProxyService','testModelAdapter'];
|
||||
const server=await createServer({root,configFile:false,optimizeDeps:{noDiscovery:true,include:[]},ssr:{noExternal:['@wailsio/runtime']},resolve:{alias:{'@':path.join(root,'src')}},server:{middlewareMode:true,hmr:false,watch:null},plugins:[{
|
||||
name:'isolated-native-bridge-fixture',enforce:'pre',
|
||||
resolveId(id){if(id==='@wailsio/runtime')return '\0review-runtime';if(id.replaceAll('\\','/').match(/\/services\/clientApi(?:\.js)?$/))return '\0review-api';},
|
||||
load(id){if(id==='\0review-runtime')return 'export const Events={On:()=>()=>{}};';if(id==='\0review-api')return apiNames.map(name=>`export async function ${name}(...args){${name==='loadUserConfig'?'return globalThis.__configReviewFixture.load();':name==='saveUserConfig'?'return globalThis.__configReviewFixture.save(args[0]);':'return {};'}}`).join('\n');},
|
||||
}]});
|
||||
try {
|
||||
const state=await server.ssrLoadModule('/src/state/appState.js');
|
||||
globalThis.window={setInterval:()=>1,clearInterval:()=>{}};
|
||||
await state.reloadUserConfig();
|
||||
assert.equal(state.appState.configCLIHTTPSListenAddr,'127.0.0.1:39999');
|
||||
const saved=await state.saveIncludeCacheWriteInHitRate(true);
|
||||
assert.equal(saved.ok,true);assert.equal(writes[0].cliHttpsListenAddr,'127.0.0.1:39999');assert.equal(writes[0].revision,'r1');
|
||||
config={...config,revision:'other-window'};
|
||||
const draft={...adapter,displayName:'unsaved draft'};
|
||||
const stale=await state.saveModelAdapterAt(0,draft,'r2','adapter-1');
|
||||
assert.equal(stale.ok,false);assert.match(stale.error,/CONFIG_CONFLICT/);assert.equal(draft.displayName,'unsaved draft');assert.equal(writes.length,1);
|
||||
const full=await state.persistUserConfig();assert.equal(full.ok,false);assert.equal(writes.length,1);
|
||||
} finally {await server.close();delete globalThis.__configReviewFixture;delete globalThis.window;}
|
||||
});
|
||||
26
frontend/test/configSafety.test.js
Normal file
26
frontend/test/configSafety.test.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {safePreferences, apiLogEntry, editorConflict} from '../src/utils/configSafety.js';
|
||||
|
||||
test('cached preferences never include model, headers, token or error bodies', () => {
|
||||
const marker = 'SENTINEL_PRIVATE_CREDENTIAL';
|
||||
const input = {revision:'revision', modelAdapters:[{apiKey:marker,customHeadersJSON:JSON.stringify({Authorization:marker}),openAIExtraParamsJSON:marker}],tabServerToken:marker,serviceLastError:marker,homeMetrics:{refreshIntervalSeconds:30,persistentMode:false,secret:marker}};
|
||||
const safe = safePreferences(input);
|
||||
assert.equal(JSON.stringify(safe).includes(marker),false);
|
||||
assert.deepEqual(safe,{homeMetrics:{includeCacheWriteInHitRate:false,persistentMode:false,refreshIntervalSeconds:30}});
|
||||
assert.deepEqual(safePreferences(safe),safe);
|
||||
});
|
||||
|
||||
test('API log records are metadata only for both success and error', () => {
|
||||
assert.deepEqual(apiLogEntry('SaveUserConfig'),{operation:'SaveUserConfig',status:'completed'});
|
||||
assert.deepEqual(apiLogEntry('SaveUserConfig',true),{operation:'SaveUserConfig',status:'failed'});
|
||||
});
|
||||
|
||||
test('editor rejects stale revision, deleted target and changed identity', () => {
|
||||
const current={revision:'new',modelAdapters:[{id:'a'},{id:'b'}]};
|
||||
assert.equal(editorConflict(current,'old',0,'a'),true);
|
||||
assert.equal(editorConflict(current,'new',2,'b'),true);
|
||||
assert.equal(editorConflict(current,'new',0,'b'),true);
|
||||
assert.equal(editorConflict(current,'new',1,'b'),false);
|
||||
assert.equal(editorConflict(current,'new',-1,''),false);
|
||||
});
|
||||
Reference in New Issue
Block a user