Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/feishu/__tests__/event-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,44 @@ describe('formatConversationTrace', () => {
});
});

// ============================================================
// formatRestartImageHints 测试 — 工作区切换重启时图片落盘路径注入
// ============================================================

const { formatRestartImageHints } = await import('../event-handler.js');

describe('formatRestartImageHints', () => {
it('should return empty string for empty paths', () => {
expect(formatRestartImageHints([])).toBe('');
});

it('should emit hint with single path', () => {
const result = formatRestartImageHints(['/tmp/feishu-downloads/abc-image.png']);
expect(result).toContain('[历史聊天图片]');
expect(result).toContain('- /tmp/feishu-downloads/abc-image.png');
expect(result).toContain('Read 工具');
});

it('should dedupe duplicate paths', () => {
const result = formatRestartImageHints([
'/tmp/feishu-downloads/a.png',
'/tmp/feishu-downloads/a.png',
'/tmp/feishu-downloads/b.png',
]);
const matches = result.match(/\/tmp\/feishu-downloads\/a\.png/g);
expect(matches?.length).toBe(1);
expect(result).toContain('/tmp/feishu-downloads/b.png');
});

it('should list multiple distinct paths', () => {
const result = formatRestartImageHints([
'/tmp/feishu-downloads/x.jpg',
'/tmp/feishu-downloads/y.png',
]);
expect(result.split('\n').filter(l => l.startsWith('- ')).length).toBe(2);
});
});

// ============================================================
// parseMessage empty @mention 测试
//
Expand Down
47 changes: 47 additions & 0 deletions src/feishu/__tests__/inject-quoted-message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ vi.mock('../../utils/image-compress.js', () => ({
})),
}));

const mockSaveMessageFileToCache = vi.fn();
vi.mock('../file-cache.js', async (importOriginal) => {
const original = await importOriginal();
return {
...original,
saveMessageFileToCache: (...args: unknown[]) => mockSaveMessageFileToCache(...args),
};
});

// ============================================================
// Tests
// ============================================================
Expand Down Expand Up @@ -191,6 +200,44 @@ describe('injectQuotedMessage', () => {
expect(result.images).toBeUndefined();
});

it('persists quoted image to cache and returns savedImagePath', async () => {
mockGetMessageById.mockResolvedValue([
{
message_id: 'imgsave',
msg_type: 'image',
body: { content: '{"image_key":"img_key_save"}' },
},
]);
const fakePng = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00]);
mockDownloadMessageImage.mockResolvedValue(fakePng);
mockSaveMessageFileToCache.mockResolvedValue('/tmp/feishu-downloads/imgsave-img_key_save.png');

const result = await injectQuotedMessage('prompt', 'imgsave', 'msg1', 'chat1');
expect(mockSaveMessageFileToCache).toHaveBeenCalledWith('imgsave', 'img_key_save', fakePng, 'image.png');
expect(result.savedImagePath).toBe('/tmp/feishu-downloads/imgsave-img_key_save.png');
// 多模态 images 仍正常返回,不受落盘影响
expect(result.images).toHaveLength(1);
});

it('returns no savedImagePath when cache save throws (non-fatal)', async () => {
mockGetMessageById.mockResolvedValue([
{
message_id: 'imgfail',
msg_type: 'image',
body: { content: '{"image_key":"img_key_fail"}' },
},
]);
const fakePng = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00]);
mockDownloadMessageImage.mockResolvedValue(fakePng);
mockSaveMessageFileToCache.mockRejectedValue(new Error('disk full'));

const result = await injectQuotedMessage('prompt', 'imgfail', 'msg1', 'chat1');
// 落盘失败不影响主流程:images 与 prompt 仍正常返回
expect(result.images).toHaveLength(1);
expect(result.prompt).toContain('引用了一张图片');
expect(result.savedImagePath).toBeUndefined();
});

it('merges quoted image with existing images', async () => {
mockGetMessageById.mockResolvedValue([
{
Expand Down
Loading
Loading