feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
This commit is contained in:
@@ -16,6 +16,9 @@ vi.mock('electron-log', () => ({
|
||||
}));
|
||||
|
||||
import { OllamaAdapter } from '../ollama.adapter';
|
||||
// v0.8.2 P0-1: 图片下载走 SSRF 安全通道(注入点替代 global fetch 打桩)
|
||||
import { __imageFetcher } from '../shared/ssrf-image-fetch';
|
||||
import type { ImageFetcher } from '../shared/ssrf-image-fetch';
|
||||
import type { MetonaRequest } from '../../types';
|
||||
import { MetonaStreamEventType } from '../../types';
|
||||
|
||||
@@ -604,48 +607,61 @@ describe('OllamaAdapter — 图片归一化', () => {
|
||||
it('http URL 图片下载为纯 base64(无 data: 前缀)', async () => {
|
||||
const adapter = makeAdapter();
|
||||
const imageBytes = new TextEncoder().encode('IMG-BYTES');
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
// v0.8.2 P0-1: 下载经 SSRF 安全通道(注入受控下载器)
|
||||
const restore = __imageFetcher.current;
|
||||
__imageFetcher.current = (async () =>
|
||||
new Response(imageBytes, {
|
||||
status: 200,
|
||||
arrayBuffer: async () => imageBytes.buffer,
|
||||
} as unknown as Response)
|
||||
.mockResolvedValue(okChatResponse());
|
||||
await adapter.send(
|
||||
makeRequest({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '看图',
|
||||
images: [{ url: 'https://example.com/pic.png' }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const body = lastBody();
|
||||
const userMsg = (body.messages as Array<Record<string, unknown>>).find(
|
||||
(m) => m.role === 'user',
|
||||
);
|
||||
expect(userMsg!.images).toEqual([Buffer.from('IMG-BYTES').toString('base64')]);
|
||||
headers: { 'content-type': 'image/png' },
|
||||
})) as unknown as ImageFetcher;
|
||||
try {
|
||||
mockFetch.mockResolvedValue(okChatResponse());
|
||||
await adapter.send(
|
||||
makeRequest({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '看图',
|
||||
images: [{ url: 'https://example.com/pic.png' }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const body = lastBody();
|
||||
const userMsg = (body.messages as Array<Record<string, unknown>>).find(
|
||||
(m) => m.role === 'user',
|
||||
);
|
||||
expect(userMsg!.images).toEqual([Buffer.from('IMG-BYTES').toString('base64')]);
|
||||
} finally {
|
||||
__imageFetcher.current = restore;
|
||||
}
|
||||
});
|
||||
|
||||
it('http URL 下载失败 → 图片被忽略(空数组/不发送),请求不阻断', async () => {
|
||||
const adapter = makeAdapter();
|
||||
mockFetch.mockRejectedValueOnce(new Error('ECONNREFUSED')).mockResolvedValue(okChatResponse());
|
||||
const res = await adapter.send(
|
||||
makeRequest({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '看图',
|
||||
images: [{ url: 'https://example.com/pic.png' }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(res.content).toBe('ok');
|
||||
const restore = __imageFetcher.current;
|
||||
__imageFetcher.current = (async () => {
|
||||
throw new Error('ECONNREFUSED');
|
||||
}) as unknown as ImageFetcher;
|
||||
try {
|
||||
mockFetch.mockResolvedValue(okChatResponse());
|
||||
const res = await adapter.send(
|
||||
makeRequest({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '看图',
|
||||
images: [{ url: 'https://example.com/pic.png' }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(res.content).toBe('ok');
|
||||
} finally {
|
||||
__imageFetcher.current = restore;
|
||||
}
|
||||
});
|
||||
|
||||
it('data URI 图片剥前缀;纯 base64 原样保留', async () => {
|
||||
@@ -675,26 +691,31 @@ describe('OllamaAdapter — 图片归一化', () => {
|
||||
|
||||
it('HTTP 下载响应非 2xx → 图片降级忽略', async () => {
|
||||
const adapter = makeAdapter();
|
||||
mockFetch
|
||||
.mockResolvedValueOnce({ ok: false, status: 404 } as unknown as Response)
|
||||
.mockResolvedValue(okChatResponse());
|
||||
await adapter.send(
|
||||
makeRequest({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '看图',
|
||||
images: [{ url: 'https://example.com/missing.png' }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const body = lastBody();
|
||||
const userMsg = (body.messages as Array<Record<string, unknown>>).find(
|
||||
(m) => m.role === 'user',
|
||||
);
|
||||
expect(userMsg!.images).toEqual([]);
|
||||
const restore = __imageFetcher.current;
|
||||
__imageFetcher.current = (async () =>
|
||||
new Response(null, { status: 404 })) as unknown as ImageFetcher;
|
||||
try {
|
||||
mockFetch.mockResolvedValue(okChatResponse());
|
||||
await adapter.send(
|
||||
makeRequest({
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: '看图',
|
||||
images: [{ url: 'https://example.com/missing.png' }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const body = lastBody();
|
||||
const userMsg = (body.messages as Array<Record<string, unknown>>).find(
|
||||
(m) => m.role === 'user',
|
||||
);
|
||||
expect(userMsg!.images).toEqual([]);
|
||||
} finally {
|
||||
__imageFetcher.current = restore;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user