mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-24 23:40:22 +08:00
239 lines
7.2 KiB
TypeScript
239 lines
7.2 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { dispatchAIChatPayload } from './aiChatPayloadDispatch';
|
|
|
|
describe('aiChatPayloadDispatch', () => {
|
|
const originalWindow = (globalThis as any).window;
|
|
|
|
afterEach(() => {
|
|
(globalThis as any).window = originalWindow;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('prefers streaming when AIChatStream is available', async () => {
|
|
const AIChatStream = vi.fn().mockResolvedValue(undefined);
|
|
const addAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
|
|
(globalThis as any).window = {
|
|
go: {
|
|
aiservice: {
|
|
Service: { AIChatStream },
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-stream',
|
|
});
|
|
|
|
expect(result).toBe('stream');
|
|
expect(AIChatStream).toHaveBeenCalledWith('session-1', [{ role: 'user', content: 'hello' }], []);
|
|
expect(addAIChatMessage).not.toHaveBeenCalled();
|
|
expect(setSending).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('appends a non-stream assistant message when only AIChatSend is available', async () => {
|
|
const AIChatSend = vi.fn().mockResolvedValue({
|
|
success: true,
|
|
content: 'done',
|
|
reasoning_content: 'thinking',
|
|
});
|
|
const addAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
const onNonStreamSuccess = vi.fn();
|
|
|
|
(globalThis as any).window = {
|
|
go: {
|
|
aiservice: {
|
|
Service: { AIChatSend },
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-send',
|
|
onNonStreamSuccess,
|
|
});
|
|
|
|
expect(result).toBe('send');
|
|
expect(addAIChatMessage).toHaveBeenCalledWith('session-1', expect.objectContaining({
|
|
id: 'msg-send',
|
|
role: 'assistant',
|
|
content: 'done',
|
|
thinking: 'thinking',
|
|
reasoning_content: 'thinking',
|
|
}));
|
|
expect(setSending).toHaveBeenCalledWith(false);
|
|
expect(onNonStreamSuccess).toHaveBeenCalled();
|
|
});
|
|
|
|
it('settles the pending assistant message when falling back to non-stream send', async () => {
|
|
const AIChatSend = vi.fn().mockResolvedValue({
|
|
success: true,
|
|
content: 'done',
|
|
reasoning_content: 'thinking',
|
|
});
|
|
const addAIChatMessage = vi.fn();
|
|
const updateAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
|
|
(globalThis as any).window = {
|
|
go: {
|
|
aiservice: {
|
|
Service: { AIChatSend },
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
updateAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-send',
|
|
pendingAssistantMessageId: 'assistant-connecting',
|
|
});
|
|
|
|
expect(result).toBe('send');
|
|
expect(addAIChatMessage).not.toHaveBeenCalled();
|
|
expect(updateAIChatMessage).toHaveBeenCalledWith('session-1', 'assistant-connecting', expect.objectContaining({
|
|
content: 'done',
|
|
thinking: 'thinking',
|
|
reasoning_content: 'thinking',
|
|
loading: false,
|
|
phase: 'idle',
|
|
}));
|
|
expect(setSending).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('emits the unavailable message when the AI service is missing', async () => {
|
|
const addAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
(globalThis as any).window = {};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-unavailable',
|
|
unavailableContent: '❌ AI Service 未就绪',
|
|
});
|
|
|
|
expect(result).toBe('unavailable');
|
|
expect(addAIChatMessage).toHaveBeenCalledWith('session-1', expect.objectContaining({
|
|
id: 'msg-unavailable',
|
|
content: '❌ AI Service 未就绪',
|
|
}));
|
|
expect(setSending).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('settles the pending assistant message when the AI service is missing', async () => {
|
|
const addAIChatMessage = vi.fn();
|
|
const updateAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
(globalThis as any).window = {};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
updateAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-unavailable',
|
|
pendingAssistantMessageId: 'assistant-connecting',
|
|
});
|
|
|
|
expect(result).toBe('unavailable');
|
|
expect(addAIChatMessage).not.toHaveBeenCalled();
|
|
expect(updateAIChatMessage).toHaveBeenCalledWith('session-1', 'assistant-connecting', expect.objectContaining({
|
|
content: '❌ AI Service 未就绪',
|
|
loading: false,
|
|
phase: 'idle',
|
|
}));
|
|
expect(setSending).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('sanitizes thrown errors and preserves the raw error when the cleaned text changes', async () => {
|
|
const AIChatSend = vi.fn().mockRejectedValue(new Error('<html><title>502 Bad Gateway</title></html>'));
|
|
const addAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
|
|
(globalThis as any).window = {
|
|
go: {
|
|
aiservice: {
|
|
Service: { AIChatSend },
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-error',
|
|
});
|
|
|
|
expect(result).toBe('error');
|
|
expect(addAIChatMessage).toHaveBeenCalledWith('session-1', expect.objectContaining({
|
|
id: 'msg-error',
|
|
content: '❌ 发送失败: HTTP 502: 502 Bad Gateway',
|
|
rawError: '<html><title>502 Bad Gateway</title></html>',
|
|
}));
|
|
expect(setSending).toHaveBeenCalledWith(false);
|
|
});
|
|
|
|
it('settles the pending assistant message when streaming startup throws', async () => {
|
|
const AIChatStream = vi.fn().mockRejectedValue(new Error('<html><title>502 Bad Gateway</title></html>'));
|
|
const addAIChatMessage = vi.fn();
|
|
const updateAIChatMessage = vi.fn();
|
|
const setSending = vi.fn();
|
|
|
|
(globalThis as any).window = {
|
|
go: {
|
|
aiservice: {
|
|
Service: { AIChatStream },
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await dispatchAIChatPayload({
|
|
sid: 'session-1',
|
|
messages: [{ role: 'user', content: 'hello' }],
|
|
tools: [],
|
|
addAIChatMessage,
|
|
updateAIChatMessage,
|
|
setSending,
|
|
nextMessageId: () => 'msg-error',
|
|
pendingAssistantMessageId: 'assistant-connecting',
|
|
});
|
|
|
|
expect(result).toBe('error');
|
|
expect(addAIChatMessage).not.toHaveBeenCalled();
|
|
expect(updateAIChatMessage).toHaveBeenCalledWith('session-1', 'assistant-connecting', expect.objectContaining({
|
|
content: '❌ 发送失败: HTTP 502: 502 Bad Gateway',
|
|
rawError: '<html><title>502 Bad Gateway</title></html>',
|
|
loading: false,
|
|
phase: 'idle',
|
|
}));
|
|
expect(setSending).toHaveBeenCalledWith(false);
|
|
});
|
|
});
|