feat: v0.7.0 四阶段全量迭代 — 修复面收口 · 安全纵深 · 架构还债 · 能力演进
P1 修复面收口: v0.6.3 截断自愈推全量(Anthropic/Ollama/非流式/引擎兜底); SSE 上游错误帧检测进重试通道; clearMessages 摘要游标根治; truncateResult 内联图片白名单统一; 前端四 bug(确认弹窗锁死/MemoryViewer/ Virtuoso Footer/abort 尾部过滤) + reasoning 缓冲跨迭代污染; 托盘通知过滤与新建会话死链接线 P2 安全纵深: MCP 审批闭环(ConfirmationHook×PolicyEngine 联动+重名拒注册); SSRF 收敛 ssrf-guard 共享模块 (web_fetch 双通道校验+重定向终态复检); Electron 加固(preload CJS 化→sandbox:true/CSP/权限白名单/will-navigate); run_command cmd.exe 白名单通道元字符守门; diff_viewer 10MB 预检; Anthropic thinking 预算下限; Agnes 思考显式关闭 P3 架构还债: OpenAICompatibleAdapter 中间基类收敛四家样板; 错误分类单轨化(删 mapError/getFetchSignal, 超时显式 ETIMEDOUT); PRAGMA user_version 迁移版本化; 死代码清理专项(cn.ts/SHORTCUTS/ContextMenu 分支/ getWindowState/modifiedArgs/sandbox 空壳); i18next 引入; a11y 第一轮; SearXNG 页批量草稿模型统一 P4 能力演进: Ollama pull 可取消/capabilities 探测/num_ctx 实测缓存; UpdateService feed 比对式自动更新 (app:updateCheck IPC + StatusBar 入口); MiMo providerOptions(web_search 服务端工具/strict JSON); web_fetch extract_mode=markdown(turndown); network.proxyUrl 全局代理(Chromium sessions+undici dispatcher) 测试: 264 → 507 用例(Electron ABI 全绿零跳过), 覆盖引擎压缩管线/重试竞速/MEMORY.md 闸门/file_editor 五操作/ filesystem 七工具实体夹具/git 真实仓库/SSE 错误帧/全线截断自愈/Provider 请求形态矩阵/SSRF 表测/钩子分级矩阵/ OutputValidator 全量/SLO 指标/MCP 安全纯函数/task_manager 链路/渲染层纯域/i18n 桥契约
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* UpdateService 测试(v0.6.4 P4-2)
|
||||
* 锁定:语义化版本比较表、feed 拉取/解析/状态映射、禁用态。
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
|
||||
vi.mock('electron-log', () => ({
|
||||
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
||||
}));
|
||||
|
||||
import { UpdateService, isNewerVersion } from '../update.service';
|
||||
|
||||
describe('isNewerVersion 语义化比较', () => {
|
||||
it.each([
|
||||
['0.7.0', '0.6.4', true],
|
||||
['1.0.0', '0.9.9', true],
|
||||
['0.6.10', '0.6.9', true],
|
||||
['0.6.4', '0.6.4', false],
|
||||
['0.6.3', '0.6.4', false],
|
||||
['v0.7.0', '0.6.4', true], // 容忍 v 前缀
|
||||
['not-a-version', '0.6.4', false], // 非法输入宁可不提示
|
||||
['', '0.6.4', false],
|
||||
// 预发布:同号正式版 > 预发布;候选预发布不提示升级
|
||||
['0.6.5-beta', '0.6.4', true],
|
||||
['0.6.4-beta', '0.6.4', false],
|
||||
['0.6.4', '0.6.4-beta', true],
|
||||
])('%s vs %s → %s', (candidate, current, expected) => {
|
||||
expect(isNewerVersion(candidate, current)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UpdateService.check 状态映射', () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('未配置 feed → disabled(不发起任何请求)', async () => {
|
||||
const fetchSpy = vi.fn();
|
||||
vi.stubGlobal('fetch', fetchSpy);
|
||||
const svc = new UpdateService(() => '0.6.4', () => null);
|
||||
const result = await svc.check();
|
||||
expect(result.status).toBe('disabled');
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('feed 声明更新版本 → available 并透传下载直链与 notes', async () => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify({ version: '0.7.0', url: 'https://dl.example/setup.exe', notes: '- fix x' }), { status: 200 }),
|
||||
),
|
||||
);
|
||||
const svc = new UpdateService(() => '0.6.4', () => 'https://update.example/feed.json');
|
||||
const result = await svc.check();
|
||||
expect(result.status).toBe('available');
|
||||
if (result.status === 'available') {
|
||||
expect(result.latestVersion).toBe('0.7.0');
|
||||
expect(result.downloadUrl).toBe('https://dl.example/setup.exe');
|
||||
expect(result.notes).toBe('- fix x');
|
||||
}
|
||||
});
|
||||
|
||||
it('同版本 / 更旧 → up-to-date', async () => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue(new Response(JSON.stringify({ version: '0.6.3' }), { status: 200 })),
|
||||
);
|
||||
const svc = new UpdateService(() => '0.6.4', () => 'https://u.example/f.json');
|
||||
const result = await svc.check();
|
||||
expect(result.status).toBe('up-to-date');
|
||||
if (result.status === 'up-to-date') expect(result.latestVersion).toBe('0.6.3');
|
||||
});
|
||||
|
||||
it('HTTP 失败与非法 JSON → error', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('oops', { status: 500 })));
|
||||
const svc = new UpdateService(() => '0.6.4', () => 'https://u.example/f.json');
|
||||
expect((await svc.check()).status).toBe('error');
|
||||
|
||||
vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response('<html/>', { status: 200 })));
|
||||
expect((await svc.check()).status).toBe('error');
|
||||
});
|
||||
|
||||
it('下载 URL 非 http(s) 时被剔除(防协议劫持)', async () => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify({ version: '9.9.9', url: 'file:///C:/evil.exe' }), { status: 200 }),
|
||||
),
|
||||
);
|
||||
const svc = new UpdateService(() => '0.6.4', () => 'https://u.example/f.json');
|
||||
const result = await svc.check();
|
||||
expect(result.status).toBe('available');
|
||||
if (result.status === 'available') expect(result.downloadUrl).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user