oh-my-muse/muse-studio/src/features/editor/components/AIPanel.contract.test.tsx
lili adc7382acb fix(muse-studio): AI SSE 流解析对齐 OpenAPI 契约
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>
2026-06-14 04:59:45 -07:00

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']);
});
});