补齐完美像素既有结果对账回归
Project CI / Repository checks (pull_request) Successful in 1m12s
Project CI / Frontend tests (pull_request) Successful in 2m56s
Project CI / Backend tests (pull_request) Successful in 3m59s
Project CI / Native shell tests (pull_request) Successful in 14m9s

补充人工重试遇到损坏稳定资源时的 GET-only 对账冲突回归。

同步后端权威契约的稳定结果短路语义。
This commit is contained in:
2026-08-13 03:49:13 +00:00
parent 27138d22a3
commit 902a6b1b33
2 changed files with 77 additions and 2 deletions
File diff suppressed because one or more lines are too long
@@ -3330,6 +3330,81 @@ describe('useImageCanvasGenerationWorkflow', () => {
});
});
it('reconciles a retried existing-result marker against its corrupted stable resource as a conflict', async () => {
const applyProjectSnapshot = vi.fn();
let corruptedProject: EditorProjectSnapshot | undefined;
snapImageToPerfectPixelsMock
.mockImplementationOnce(async (request: EditorPixelArtSnapInput) =>
createMismatchedPerfectPixelResult(request),
)
.mockImplementationOnce(async (request: EditorPixelArtSnapInput) => {
const operationId = request.canvasCompletion.dialogId;
corruptedProject = createPerfectPixelProject(
operationId,
'dialog-missing',
);
corruptedProject.resources[0] = {
...corruptedProject.resources[0]!,
taskId: 'pixel-art-snap-corrupted-task',
objectKey: null,
};
throw new ApiClientError({
message: '同一完美像素操作已有权威结果,请先读取项目状态对账。',
status: 409,
code: 'HTTP_409',
details: {
operationResultAlreadyExists: true,
resultResourceId: corruptedProject.resources[0]!.resourceId,
},
});
});
loadEditorProjectMock.mockImplementation(async () => {
const request = snapImageToPerfectPixelsMock.mock.calls.at(-1)?.[0] as
| EditorPixelArtSnapInput
| undefined;
return snapImageToPerfectPixelsMock.mock.calls.length === 1
? createConflictingPerfectPixelProject(
request!.canvasCompletion.dialogId,
)
: corruptedProject!;
});
render(
<GenerationWorkflowHarness
projectId="project-1"
initialLayers={[
createLayer({
objectKey: 'generated-images/editor/source.png',
src: '/generated-images/editor/source.png',
}),
]}
applyProjectSnapshot={applyProjectSnapshot}
flushProjectPersistence={vi.fn(async () => {})}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '完美像素' }));
await waitFor(() => {
expect(screen.getByTestId('dialog').textContent).toContain(
'pending-confirmation',
);
});
fireEvent.click(screen.getByRole('button', { name: '重试完美像素' }));
await waitFor(() => {
expect(screen.getByTestId('dialog-error').textContent).toContain(
'完美像素任务资源记录不完整或不属于当前项目,无法自动确认结果。',
);
});
expect(snapImageToPerfectPixelsMock).toHaveBeenCalledTimes(2);
expect(loadEditorProjectMock).toHaveBeenCalledTimes(2);
expect(applyProjectSnapshot).not.toHaveBeenCalled();
expect(screen.getByTestId('dialog').textContent).toContain(
'pending-confirmation',
);
});
it('anchors the first submission reconciliation window at POST time when the pre-POST flush is slow', async () => {
// 中文注释:pre-POST flush 是服务端硬前置(占位未持久化会被 409 拒收),且没有整体
// 上限。窗口若锚在 flush 之前,慢保存会让 POST 带着已过期的 reconciliation deadline