connectAIStream 改用统一 createEventStreamParser,按 SSE event 字段(而非内层 data.type)派发; onDone 载荷对齐 SSEDoneEvent.data(taskId/suggestionId 为 int64、summary 可选); MSW mock 同步发 "event: chunk/done"(sequenceNo 从 1 递增)。 修复 connectAIStream 契约漂移——此前被 mock 掩盖、真实联调会失败。 注:此为本会话之前已存在的未提交改动,经 review 后据你确认提交;studio vitest/playwright 未在本环境运行。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import React from 'react';
|
|
import { http, HttpResponse } from 'msw';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import AIPanel from './AIPanel';
|
|
import { server } from '@/test/setup';
|
|
|
|
describe('AIPanel task stream contract', () => {
|
|
it('creates an AI task with JSON body and streams by taskId without leaking prompt into URL', async () => {
|
|
const createRequests: Array<{ url: string; body: unknown }> = [];
|
|
const streamRequests: string[] = [];
|
|
|
|
server.use(
|
|
http.post('/app-api/muse/ai/tasks', async ({ request }) => {
|
|
createRequests.push({
|
|
url: request.url,
|
|
body: await request.json(),
|
|
});
|
|
return HttpResponse.json({
|
|
code: 0,
|
|
msg: 'success',
|
|
data: {
|
|
taskId: 'task-123',
|
|
},
|
|
}, { status: 202 });
|
|
}),
|
|
http.get('/app-api/muse/ai/tasks/:taskId/stream', ({ request }) => {
|
|
streamRequests.push(request.url);
|
|
const encoder = new TextEncoder();
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
controller.enqueue(encoder.encode('event: chunk\ndata: {"content":"星海","sequenceNo":1}\n\n'));
|
|
controller.enqueue(encoder.encode('event: done\ndata: {"taskId":123,"suggestionId":456,"summary":"完成"}\n\n'));
|
|
controller.close();
|
|
},
|
|
});
|
|
|
|
return new HttpResponse(stream, {
|
|
headers: { 'Content-Type': 'text/event-stream' },
|
|
});
|
|
})
|
|
);
|
|
|
|
const onCandidateGenerated = vi.fn();
|
|
render(React.createElement(AIPanel, { onCandidateGenerated }));
|
|
|
|
fireEvent.change(screen.getByPlaceholderText(/在此处输入创作描述/), {
|
|
target: { value: '秘密提示词' },
|
|
});
|
|
fireEvent.click(screen.getByRole('button', { name: /发送指令/ }));
|
|
|
|
await waitFor(() => expect(onCandidateGenerated).toHaveBeenCalledWith('星海'));
|
|
expect(createRequests).toHaveLength(1);
|
|
expect(createRequests[0]?.url).not.toContain('秘密提示词');
|
|
expect(createRequests[0]?.body).toMatchObject({
|
|
intent: {
|
|
kind: 'continuation',
|
|
userInstruction: '秘密提示词',
|
|
outputTarget: 'suggestion',
|
|
},
|
|
contextScope: 'work',
|
|
});
|
|
expect(streamRequests).toEqual(['http://localhost:3000/app-api/muse/ai/tasks/task-123/stream']);
|
|
});
|
|
});
|