修复退款复核与发布安全边界

补齐退款人工复核、刷新恢复、会话隔离和旧冷备导入兼容
强化真实微信退款对账开关、重复环境变量和生产发布门禁
优化公开作品资产授权查询并保持实时撤销语义
统一后台微信渠道、退款追回流水和画布正式资源测试契约
补齐生成与素材侧栏回归测试
This commit is contained in:
2026-07-14 16:31:22 +08:00
parent aa56c117ec
commit 0baa8cf8cc
34 changed files with 2047 additions and 205 deletions
@@ -3,7 +3,7 @@
import {
act,
fireEvent,
render,
render as testingLibraryRender,
screen,
waitFor,
within,
@@ -22,6 +22,12 @@ import {
setupImageCanvasEditorViewTestLifecycle,
} from './ImageCanvasEditorView.test-utils';
function render(ui: Parameters<typeof testingLibraryRender>[0]) {
const result = testingLibraryRender(ui);
fireEvent.click(screen.getByRole('button', { name: '打开素材' }));
return result;
}
const generateEditorImageMock = vi.hoisted(() => vi.fn());
const generateEditorIconSpritesheetMock = vi.hoisted(() => vi.fn());
const generateEditorCharacterAnimationMock = vi.hoisted(() => vi.fn());
@@ -39,6 +39,7 @@ const loadEditorProjectMock = vi.hoisted(() => vi.fn());
const loadOrCreateRecentEditorProjectMock = vi.hoisted(() => vi.fn());
const renameEditorProjectMock = vi.hoisted(() => vi.fn());
const saveEditorProjectLayoutMock = vi.hoisted(() => vi.fn());
const uploadEditorMediaAssetFileMock = vi.hoisted(() => vi.fn());
vi.mock('../../services/image-editor/editorProjectClient', async () => {
const actual = await vi.importActual<
@@ -71,6 +72,10 @@ vi.mock('./ImageCanvasUiAssetExtractionRasterModel', () => ({
renderUiDesignAssetExtractionMarkedImageMock,
}));
vi.mock('../../services/image-editor/editorMediaAssetUploadClient', () => ({
uploadEditorMediaAssetFile: uploadEditorMediaAssetFileMock,
}));
vi.mock('./ImageCanvasProjectCoverSnapshotModel', async () => {
const actual = await vi.importActual<
typeof import('./ImageCanvasProjectCoverSnapshotModel')
@@ -106,8 +111,14 @@ describe('ImageCanvasEditorView generation integration', () => {
window.localStorage.clear();
renderUiDesignAssetExtractionMarkedImageMock.mockReset();
renderUiDesignAssetExtractionMarkedImageMock.mockResolvedValue(
'data:image/png;base64,marked-ui-design',
'data:image/png;base64,bWFya2VkLXVpLWRlc2lnbg==',
);
uploadEditorMediaAssetFileMock.mockReset();
uploadEditorMediaAssetFileMock.mockResolvedValue({
objectKey:
'generated-character-drafts/editor/generation-references/marked-ui-design.png',
assetObjectId: 'asset-object-marked-ui-design',
});
});
const openGenerationDimensionsPanel = (
@@ -1344,6 +1355,13 @@ describe('ImageCanvasEditorView generation integration', () => {
clientY: 120,
},
);
fireEvent.change(
within(screen.getByRole('dialog', { name: '生成图标素材' })).getByRole(
'textbox',
{ name: '素材描述' },
),
{ target: { value: '返回按钮' } },
);
fireEvent.click(
within(screen.getByRole('dialog', { name: '生成图标素材' })).getByRole(
'button',
@@ -1351,7 +1369,11 @@ describe('ImageCanvasEditorView generation integration', () => {
),
);
expect(screen.queryByRole('dialog', { name: '生成图标素材' })).toBeNull();
await waitFor(() => {
expect(
screen.queryByRole('dialog', { name: '生成图标素材' }),
).toBeNull();
});
const frame = screen.getByLabelText('图标素材生成占位图');
expect(frame.className).toContain(
'image-canvas-editor__generation-frame--generating',
@@ -1743,6 +1765,13 @@ describe('ImageCanvasEditorView generation integration', () => {
screen.getByRole('dialog', { name: '生成图标素材' }),
'图标规范 2',
);
fireEvent.change(
within(screen.getByRole('dialog', { name: '生成图标素材' })).getByRole(
'textbox',
{ name: '素材描述' },
),
{ target: { value: '返回按钮' } },
);
fireEvent.click(
within(screen.getByRole('dialog', { name: '生成图标素材' })).getByRole(
'button',
@@ -1990,7 +2019,12 @@ describe('ImageCanvasEditorView generation integration', () => {
{ target: { value: '返回按钮\n设置按钮\n提示按钮' } },
);
expect(within(iconPanel).getAllByRole('textbox')).toHaveLength(1);
expect(
within(iconPanel).getByRole('textbox', { name: '素材描述' }).tagName,
).toBe('TEXTAREA');
expect(
within(iconPanel).getByRole('textbox', { name: '资源名称' }),
).toBeTruthy();
expect(
within(iconPanel).queryByRole('button', { name: '添加素材描述' }),
).toBeNull();
@@ -2389,8 +2423,8 @@ describe('ImageCanvasEditorView generation integration', () => {
aspectRatio: '1:1',
imageSize: '1K',
referenceImageSrcs: [
'data:image/png;base64,cm9sZS1zcGVj',
'data:image/png;base64,YmlnLWZpc2gtcmVm',
'resource-character-spec',
'resource-big-fish',
],
projectId: 'editor-project-default',
assetFolderId: 'project',
@@ -2759,6 +2793,7 @@ describe('ImageCanvasEditorView generation integration', () => {
await waitFor(() => {
expect(loadEditorAssetLibraryMock).toHaveBeenCalledTimes(2);
});
fireEvent.click(screen.getByRole('button', { name: '打开素材' }));
expect(
await screen.findByRole('button', { name: '添加清爽按钮素材图集' }),
).toBeTruthy();
@@ -3038,7 +3073,7 @@ describe('ImageCanvasEditorView generation integration', () => {
await waitFor(() => {
expect(generateEditorIconSpritesheetMock).toHaveBeenCalledWith(
expect.objectContaining({
referenceImageSrc: 'data:image/png;base64,cmVuYW1lZC1pY29uLXNwZWM=',
referenceImageSrc: 'resource-editor-icon-spec-renamed-1',
iconDescriptions: ['背包按钮'],
model: 'gemini-3.1-flash-image-preview',
aspectRatio: '1:1',
@@ -3258,20 +3293,22 @@ describe('ImageCanvasEditorView generation integration', () => {
).toBeTruthy();
fireEvent.click(within(panel).getByRole('button', { name: '生成120泥点' }));
expect(generateEditorCharacterAnimationMock).toHaveBeenCalledWith(
expect.objectContaining({
sourceLayerId: 'layer-character',
sourceImageSrc:
'generated-character-drafts/editor/character-images/source/image.png',
sourceWidth: 1024,
sourceHeight: 1024,
resolution: '720p',
ratio: '16:9',
frameCount: 48,
durationSeconds: 6,
model: 'seedance2.0-fast',
}),
);
await waitFor(() => {
expect(generateEditorCharacterAnimationMock).toHaveBeenCalledWith(
expect.objectContaining({
sourceLayerId: 'layer-character',
sourceImageSrc:
'generated-character-drafts/editor/character-images/source/image.png',
sourceWidth: 1024,
sourceHeight: 1024,
resolution: '720p',
ratio: '16:9',
frameCount: 48,
durationSeconds: 6,
model: 'seedance2.0-fast',
}),
);
});
expect(
generateEditorCharacterAnimationMock.mock.calls[0]?.[0]?.promptText,
).toBe(precisePrompt);
@@ -3429,7 +3466,8 @@ describe('ImageCanvasEditorView generation integration', () => {
await waitFor(() => {
expect(extractEditorUiDesignAssetsMock).toHaveBeenCalledWith(
expect.objectContaining({
sourceImageSrc: 'data:image/png;base64,marked-ui-design',
sourceImageSrc:
'generated-character-drafts/editor/generation-references/marked-ui-design.png',
screenColor: 'auto',
segModel: 'birefnet',
aspectRatio: '1:1',
@@ -81,6 +81,9 @@ test('profile funds ViewModel resolves ledger source labels with raw fallback',
expect(getWalletLedgerSourceLabel('puzzle_author_incentive_claim')).toBe(
'拼图作者奖励',
);
expect(getWalletLedgerSourceLabel('recharge_refund_recovery')).toBe(
'充值退款追回',
);
expect(getWalletLedgerSourceLabel('future_source')).toBe('future_source');
expect(getWalletLedgerSourceLabel('')).toBe('未知来源');
});
@@ -98,9 +101,18 @@ test('profile funds ViewModel builds wallet ledger presentation', () => {
balanceAfter: 79,
sourceType: 'asset_operation_consume',
});
const recoveryEntry = buildLedgerEntry({
id: 'ledger-recovery',
amountDelta: -30,
balanceAfter: 49,
sourceType: 'recharge_refund_recovery',
});
expect(
buildWalletLedgerPresentation({ entries: [incomeEntry, outcomeEntry] }, 12),
buildWalletLedgerPresentation(
{ entries: [incomeEntry, outcomeEntry, recoveryEntry] },
12,
),
).toEqual({
balance: 80,
balanceLabel: '80泥点',
@@ -121,6 +133,14 @@ test('profile funds ViewModel builds wallet ledger presentation', () => {
isIncome: false,
sourceLabel: '资产操作消耗',
},
{
amountLabel: '-30',
balanceLabel: '余额 49',
createdAt: '2026-06-03T00:00:00.000Z',
id: 'ledger-recovery',
isIncome: false,
sourceLabel: '充值退款追回',
},
],
});
@@ -17,6 +17,7 @@ const PROFILE_WALLET_LEDGER_SOURCE_LABELS = {
daily_free_reset: '每日免费重置',
asset_operation_consume: '资产操作消耗',
asset_operation_refund: '资产操作退回',
recharge_refund_recovery: '充值退款追回',
redeem_code_reward: '兑换码奖励',
puzzle_author_incentive_claim: '拼图作者奖励',
daily_task_reward: '每日任务奖励',