3D 提交失败路径也刷新钱包余额

- src/components/image-editor/model3d-generation/useModel3dGenerationTask.ts:请求失败与「2xx 但没有任务状态」两条路径都调用 onWalletBalanceMayHaveChanged,与其它画布生成链路的 runEditorGenerationWithWalletRefresh 同一口径(超时或空 body 的 2xx 都可能已经扣费)
- src/components/image-editor/model3d-generation/useModel3dGenerationTask.test.tsx:补两条失败路径各刷新一次钱包的断言
This commit is contained in:
2026-09-24 17:31:28 +08:00
parent 5ba7a993de
commit 3455b7a00f
2 changed files with 35 additions and 1 deletions
@@ -99,11 +99,13 @@ function renderTask({
projectId = 'project-1',
assetFolderId,
applyQueuedGeneration = vi.fn(async () => true),
onWalletBalanceMayHaveChanged = vi.fn(),
}: {
dialog: GenerateDialogState;
projectId?: string | null;
assetFolderId?: string | null;
applyQueuedGeneration?: (...args: unknown[]) => Promise<boolean>;
onWalletBalanceMayHaveChanged?: () => void;
}) {
const onQueuedGenerationTask = vi.fn();
const view = renderHook(() => {
@@ -126,6 +128,7 @@ function renderTask({
currentDialog.placeholder ?? null,
applyQueuedGeneration: applyQueuedGeneration as never,
onQueuedGenerationTask,
onWalletBalanceMayHaveChanged,
});
return {
submitModel3dGeneration,
@@ -133,7 +136,7 @@ function renderTask({
dialog: dialogs[0] as GenerateDialogState,
};
});
return { ...view, onQueuedGenerationTask };
return { ...view, onQueuedGenerationTask, onWalletBalanceMayHaveChanged };
}
describe('useModel3dGenerationTask', () => {
@@ -293,6 +296,32 @@ describe('useModel3dGenerationTask', () => {
expect(submitModel3dTextToModelRequestMock).not.toHaveBeenCalled();
});
it('提交失败与「2xx 无任务状态」都按可能已扣费刷新钱包', async () => {
submitModel3dTextToModelRequestMock.mockRejectedValue(
new ApiClientError({
message: '3D 模型生成失败,请稍后重试。',
status: 502,
code: 'tripo-upstream-error',
}),
);
const failed = renderTask({ dialog: createDialog() });
await act(async () => {
await failed.result.current.submitModel3dGeneration(createDialog());
});
expect(failed.onWalletBalanceMayHaveChanged).toHaveBeenCalledTimes(1);
submitModel3dTextToModelRequestMock.mockResolvedValue(null);
const emptyBody = renderTask({ dialog: createDialog() });
await act(async () => {
await emptyBody.result.current.submitModel3dGeneration(createDialog());
});
expect(emptyBody.onWalletBalanceMayHaveChanged).toHaveBeenCalledTimes(1);
});
it('定价段缺失时不发请求,只把不可提交的原因写回面板', async () => {
applyEditorGenerationPricingConfig({
models: EDITOR_MODEL_MUD_POINT_CONFIG,
@@ -127,6 +127,8 @@ export function useModel3dGenerationTask({
if (!queueState) {
// 2xx 但没有任务状态(空 body 或缺 status 的 payload)不能当成功静默复位:
// 用户既看不到任务也没有任何提示,服务端细节被丢掉。
// 服务端在这条路上也可能已经扣过费,钱包余额同样要按「可能变了」刷新。
onWalletBalanceMayHaveChanged?.();
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
...currentDialog,
status: 'failed',
@@ -155,6 +157,9 @@ export function useModel3dGenerationTask({
errorMessage: undefined,
}));
} catch (error) {
// 超时等失败也可能意味着服务端已经扣费(与其它生成链路的
// runEditorGenerationWithWalletRefresh 同一口径),失败路径也要刷新钱包。
onWalletBalanceMayHaveChanged?.();
updateCanvasGenerationDialogById(dialogId, (currentDialog) => ({
...currentDialog,
status: 'failed',