完成完美像素前端未知结果收口

改为 object-only 源图上传并贯穿取消信号
以项目 GET 正向证据判定画布成功与 asset-only
持久化并原样重放 operation 请求,阻止未收口身份被删除
统一 75 秒绝对窗口并兼容旧 240 秒 journal
补齐时钟偏差、删除保护与 byte-for-byte 回归测试
同步更新图片画布契约与共享决策记录
This commit is contained in:
2026-08-04 03:59:59 +00:00
parent 7f13af6135
commit f99abce109
16 changed files with 1219 additions and 425 deletions
@@ -35,7 +35,7 @@ describe('editorMediaAssetUploadClient', () => {
vi.unstubAllGlobals();
});
it('uploads editor MP4 assets through direct OSS upload and confirms object metadata', async () => {
it('uploads editor MP4 assets and forwards one abort signal through signed-url resolution', async () => {
requestJsonMock
.mockResolvedValueOnce({
upload: {
@@ -63,16 +63,19 @@ describe('editorMediaAssetUploadClient', () => {
getSignedAssetReadUrlMock.mockResolvedValueOnce(
'https://signed.example.com/demo.mp4',
);
const abortController = new AbortController();
const result = await uploadEditorMediaAssetFile(
new File(['video'], 'demo.mp4', { type: 'video/mp4' }),
'video',
{ signal: abortController.signal },
);
expect(requestJsonMock).toHaveBeenNthCalledWith(
1,
'/api/assets/direct-upload-tickets',
expect.objectContaining({
signal: abortController.signal,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: expect.any(String),
@@ -99,6 +102,7 @@ describe('editorMediaAssetUploadClient', () => {
expect(fetch).toHaveBeenCalledWith(
'https://oss.example.com',
expect.objectContaining({
signal: abortController.signal,
method: 'POST',
body: expect.any(FormData),
}),
@@ -110,6 +114,7 @@ describe('editorMediaAssetUploadClient', () => {
2,
'/api/assets/objects/confirm',
expect.objectContaining({
signal: abortController.signal,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: expect.any(String),
@@ -119,7 +124,8 @@ describe('editorMediaAssetUploadClient', () => {
);
expect(confirmBody).toMatchObject({
bucket: 'bucket',
objectKey: 'generated-character-drafts/editor/asset-library/video/demo.mp4',
objectKey:
'generated-character-drafts/editor/asset-library/video/demo.mp4',
contentType: 'video/mp4',
contentLength: 5,
assetKind: 'editor_uploaded_video',
@@ -132,7 +138,7 @@ describe('editorMediaAssetUploadClient', () => {
'generated-character-drafts/editor/asset-library/video/demo.mp4',
expireSeconds: 3600,
},
undefined,
abortController.signal,
{ bypassCache: true },
);
expect(result).toEqual({
@@ -194,7 +200,8 @@ describe('editorMediaAssetUploadClient', () => {
(requestJsonMock.mock.calls[1]?.[1] as RequestInit).body as string,
);
expect(confirmBody).toMatchObject({
objectKey: 'generated-character-drafts/editor/asset-library/image/spec.png',
objectKey:
'generated-character-drafts/editor/asset-library/image/spec.png',
contentType: 'image/png',
contentLength: 5,
assetKind: 'editor_uploaded_image',
@@ -260,12 +267,7 @@ describe('editorMediaAssetUploadClient', () => {
(requestJsonMock.mock.calls[0]?.[1] as RequestInit).body as string,
);
expect(ticketBody).toMatchObject({
pathSegments: [
'editor',
'project-covers',
'project-1',
'1771400000000',
],
pathSegments: ['editor', 'project-covers', 'project-1', '1771400000000'],
fileName: 'project-1-cover.png',
metadata: {
asset_kind: 'editor_project_cover_snapshot',
@@ -166,7 +166,7 @@ export async function uploadEditorMediaAssetFile(
objectKey: uploaded.objectKey,
expireSeconds: EDITOR_MEDIA_READ_EXPIRE_SECONDS,
},
undefined,
options.signal,
{ bypassCache: true },
);
return {