Merge branch 'main' of https://git.nianxx.cn/wangxuming/makelore
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
2026-08-17 22:40:44 +08:00
2 changed files with 179 additions and 26 deletions

View File

@@ -6,6 +6,32 @@ describe('Learning Agent client', () => {
const fetchImpl = vi.fn<typeof fetch>();
const getAccessToken = vi.fn();
const chunkedStream = (...chunks: string[]) => new Response(new ReadableStream<Uint8Array>({
start(controller) {
const encoder = new TextEncoder();
for (const chunk of chunks) controller.enqueue(encoder.encode(chunk));
controller.close();
},
}), { status: 200, headers: { 'Content-Type': 'text/event-stream' } });
const mockRun = (stream: Response) => {
fetchImpl
.mockResolvedValueOnce(new Response(JSON.stringify({ session_id: 'session-1' }), { status: 201 }))
.mockResolvedValueOnce(new Response(JSON.stringify({ run_id: 'run-1' }), { status: 202 }))
.mockResolvedValueOnce(new Response(JSON.stringify({ stream_url: '/api/agents/sessions/session-1/events?ticket=ticket-1' }), { status: 200 }))
.mockResolvedValueOnce(stream);
};
const ask = () => createLearningAgentClient({
fetchImpl,
getAccessToken,
apiBaseUrl: 'https://square.example',
}).ask({
courseId: 'course-1',
contentHash: 'a'.repeat(64),
message: '为什么要学 Python',
});
beforeEach(() => {
fetchImpl.mockReset();
getAccessToken.mockReset();
@@ -68,6 +94,60 @@ describe('Learning Agent client', () => {
expect(fetchImpl.mock.calls.some((call) => call[1]?.method === 'DELETE')).toBe(false);
});
it('parses production CRLF-delimited SSE frames', async () => {
const event = (type: string, payload: Record<string, unknown>) => (
`id: 1\r\nevent: agent.event\r\ndata: ${JSON.stringify({ run_id: 'run-1', type, payload })}\r\n\r\n`
);
mockRun(chunkedStream(
event('learning.assistant.delta', { delta: 'CRLF 正常。' }),
event('learning.assistant.completed', {}),
));
await expect(ask()).resolves.toEqual({ text: 'CRLF 正常。' });
});
it('parses a CRLF frame delimiter split across stream chunks', async () => {
const delta = JSON.stringify({
run_id: 'run-1',
type: 'learning.assistant.delta',
payload: { delta: '跨块正常。' },
});
const completed = JSON.stringify({
run_id: 'run-1',
type: 'learning.assistant.completed',
payload: {},
});
mockRun(chunkedStream(
`data: ${delta}\r\n\r`,
`\ndata: ${completed}\r`,
'\n\r',
'\n',
));
await expect(ask()).resolves.toEqual({ text: '跨块正常。' });
});
it('joins multiple data lines and ignores other SSE fields and comments', async () => {
const delta = JSON.stringify({
run_id: 'run-1',
type: 'learning.assistant.delta',
payload: { delta: '多行正常。' },
});
const split = delta.indexOf(',"type"');
const completed = JSON.stringify({
run_id: 'run-1',
type: 'learning.assistant.completed',
payload: {},
});
mockRun(chunkedStream(
': keep-alive\r\nid: 9\r\nevent: agent.event\r\nretry: 3000\r\n',
`data: ${delta.slice(0, split)},\r\ndata: ${delta.slice(split + 1)}\r\n\r\n`,
`data: ${completed}\r\n\r\n`,
));
await expect(ask()).resolves.toEqual({ text: '多行正常。' });
});
it('stops before the network for an invalid package hash', async () => {
const client = createLearningAgentClient({ fetchImpl, getAccessToken });
await expect(client.ask({ courseId: 'course-1', contentHash: 'bad', message: '你好' })).rejects.toThrow('助教请求无效');