修复确认生成后的任务列表对账

问题:客户端确认 Quote 后只刷新一次任务,遇到短暂投影延迟时任务列表为空且流程静默成功。

修复:按 quoteId 有界重试任务列表,合并 REST 与实时推送快照,缺任务时显示明确错误;补充 Store 与页面回归测试。
This commit is contained in:
2026-08-03 15:48:04 +08:00
parent 78e4de91e4
commit fab0e4034c
4 changed files with 92 additions and 2 deletions

View File

@@ -14,6 +14,7 @@ const fetchImageWorkspaceProjectMock = vi.hoisted(() => vi.fn());
const fetchImageWorkspaceTasksMock = vi.hoisted(() => vi.fn());
const openImageWorkspaceTaskEventsMock = vi.hoisted(() => vi.fn());
const sendImageWorkspaceMessageMock = vi.hoisted(() => vi.fn());
const confirmImageWorkspaceGenerationMock = vi.hoisted(() => vi.fn());
vi.mock('@/lib/image-workspace', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/lib/image-workspace')>();
@@ -24,6 +25,9 @@ vi.mock('@/lib/image-workspace', async (importOriginal) => {
fetchImageWorkspaceTasks: (...args: unknown[]) => fetchImageWorkspaceTasksMock(...args),
openImageWorkspaceTaskEvents: (...args: unknown[]) => openImageWorkspaceTaskEventsMock(...args),
sendImageWorkspaceMessage: (...args: unknown[]) => sendImageWorkspaceMessageMock(...args),
confirmImageWorkspaceGeneration: (...args: unknown[]) => (
confirmImageWorkspaceGenerationMock(...args)
),
};
});
@@ -144,6 +148,10 @@ describe('AI design task event store', () => {
fetchImageWorkspaceProjectMock.mockResolvedValue(workspace());
fetchImageWorkspaceTasksMock.mockResolvedValue([task]);
sendImageWorkspaceMessageMock.mockResolvedValue(workspace());
confirmImageWorkspaceGenerationMock.mockResolvedValue({
...workspace('workspace-one', 2),
turnRevision: 2,
});
});
afterEach(() => {
@@ -236,6 +244,46 @@ describe('AI design task event store', () => {
await sending;
});
it('reconciles the confirmed Quote until its generation task is visible', async () => {
const source = new MockEventSource();
openImageWorkspaceTaskEventsMock.mockResolvedValue(source as unknown as EventSource);
fetchImageWorkspaceTasksMock
.mockResolvedValueOnce([])
.mockResolvedValueOnce([])
.mockResolvedValueOnce([task]);
await useImageWorkspaceStore.getState().load();
await useImageWorkspaceStore.getState().confirmGeneration('quote-one');
expect(confirmImageWorkspaceGenerationMock).toHaveBeenCalledWith(
'workspace-one',
1,
'quote-one',
expect.stringMatching(/^turn-/),
);
expect(fetchImageWorkspaceTasksMock).toHaveBeenCalledTimes(3);
expect(useImageWorkspaceStore.getState().tasks).toEqual([task]);
});
it('does not silently complete confirmation when the quoted task stays missing', async () => {
const source = new MockEventSource();
openImageWorkspaceTaskEventsMock.mockResolvedValue(source as unknown as EventSource);
fetchImageWorkspaceTasksMock.mockResolvedValue([]);
await useImageWorkspaceStore.getState().load();
vi.useFakeTimers();
const outcome = useImageWorkspaceStore.getState().confirmGeneration('quote-one').then(
() => null,
(error: unknown) => error,
);
await vi.runAllTimersAsync();
await expect(outcome).resolves.toMatchObject({ code: 'generation_task_not_visible' });
expect(fetchImageWorkspaceTasksMock).toHaveBeenCalledTimes(6);
expect(useImageWorkspaceStore.getState().error)
.toContain('任务列表同步超时');
});
it('closes the previous stream when switching workspaces and on reset', async () => {
const first = new MockEventSource();
const second = new MockEventSource();