diff --git a/services/api/test/uploads.e2e-spec.ts b/services/api/test/uploads.e2e-spec.ts index c1c1399..851a2ee 100644 --- a/services/api/test/uploads.e2e-spec.ts +++ b/services/api/test/uploads.e2e-spec.ts @@ -676,10 +676,12 @@ describe('uploads renew & complete & history (e2e)', () => { }); it('complete:contentType 不符(presign 不签名 content-type,服务端兜底)→ 400', async () => { - const init = await initAs(memberToken, 'C-type'); + // bytes=1002:C-bytes 完成后本册已有 member 的 1000 字节非 deleted 照片, + // 沿用 1000 会被 init ③.5 服务端查重拦截(90dcc58),换字节数绕开。 + const init = await initAs(memberToken, 'C-type', { bytes: 1002 }); // 声明 image/jpeg,实传 application/octet-stream:PUT 成功(签名不覆盖该头), // complete 时 head().contentType 与申明值比对兜底。 - expect(await putStaging(init.credential, 1000, 'application/octet-stream')).toBe(true); + expect(await putStaging(init.credential, 1002, 'application/octet-stream')).toBe(true); const res = await complete(memberToken, init.uploadId); expect(res.status).toBe(400); expect(res.body.code).toBe(ErrorCodes.UPLOAD_VERIFY_FAILED); @@ -690,9 +692,10 @@ describe('uploads renew & complete & history (e2e)', () => { }); it('complete 成功 → 200 uploaded;photo/session/photo_jobs/audit 同事务落库;重复 complete 幂等(A03)', async () => { - const init = await initAs(memberToken, 'C-happy'); + // bytes=1003:同上,避开 C-bytes 已完成的 1000 字节照片(init ③.5 查重)。 + const init = await initAs(memberToken, 'C-happy', { bytes: 1003 }); cHappy = { uploadId: init.uploadId, photoId: init.photoId, objectKey: init.objectKey }; - expect(await putStaging(init.credential, 1000)).toBe(true); + expect(await putStaging(init.credential, 1003)).toBe(true); const res = await complete(memberToken, init.uploadId); expect(res.status).toBe(200); @@ -702,7 +705,7 @@ describe('uploads renew & complete & history (e2e)', () => { // DB 四处一致性。 const photo = await prisma.photos.findUnique({ where: { id: init.photoId } }); expect(photo).toMatchObject({ - bytes: 1000, + bytes: 1003, uploadedAt: expect.any(Date), processingState: 'uploaded', visibility: 'unpublished', @@ -749,8 +752,8 @@ describe('uploads renew & complete & history (e2e)', () => { const stagingHead = await storage.head(cHappy.objectKey); const finalHead = await storage.head(photo?.objectKey as string); - expect(stagingHead?.bytes).toBe(1000); - expect(finalHead?.bytes).toBe(1000); + expect(stagingHead?.bytes).toBe(1003); + expect(finalHead?.bytes).toBe(1003); // 手工删除 staging(模拟生命周期清理/超时)后重复 complete:originals 已在, // copyIfAbsent 幂等返回 created:false,不再依赖 staging 源。 @@ -779,7 +782,7 @@ describe('uploads renew & complete & history (e2e)', () => { const res = await complete(memberToken, cHappy.uploadId); expect(res.status).toBe(200); expect(res.body.processingState).toBe('uploaded'); - expect((await storage.head(photo?.objectKey as string))?.bytes).toBe(1000); + expect((await storage.head(photo?.objectKey as string))?.bytes).toBe(1003); }); it('GET /uploads:本人会话新在前,photo 状态内嵌,游标翻页,跨用户隔离', async () => { @@ -823,7 +826,7 @@ describe('uploads renew & complete & history (e2e)', () => { expect(page1.body.items[0]).toMatchObject({ state: 'confirmed', albumId, - expectedBytes: 1000, + expectedBytes: 1003, contentType: 'image/jpeg', photo: { photoId: cHappy.photoId, @@ -895,8 +898,10 @@ describe('uploads renew & complete & history (e2e)', () => { }); it('sourceDeviceId:init 携带 → 落库并回显;幂等重放换设备保留首值(first-writer)', async () => { - // 设备 A 初始化(P3 Task 3 契约增补,A19 设备来源语义)。 + // 设备 A 初始化(P3 Task 3 契约增补,A19 设备来源语义)。bytes=1004: + // 避开本册已完成的 1000(C-bytes)字节数(init ③.5 查重)。 const first = await initAs(memberToken, 'DEV-1', { + bytes: 1004, sourceDeviceId: 'device-aaaa-1111', }); @@ -912,13 +917,14 @@ describe('uploads renew & complete & history (e2e)', () => { // 同 clientTaskId 换设备 B 重放:复用原会话,sourceDeviceId 保留首值。 const replay = await initAs(memberToken, 'DEV-1', { + bytes: 1004, sourceDeviceId: 'device-bbbb-2222', }); expect(replay.uploadId).toBe(first.uploadId); expect((await findByTask('DEV-1'))?.sourceDeviceId).toBe('device-aaaa-1111'); - // 未携带 → null(老客户端兼容)。 - await initAs(memberToken, 'DEV-2'); + // 未携带 → null(老客户端兼容)。bytes=1005 同理避开已完成字节数。 + await initAs(memberToken, 'DEV-2', { bytes: 1005 }); expect((await findByTask('DEV-2'))?.sourceDeviceId).toBeNull(); // 长度越界(<8 / >64)→ 400 VALIDATION_FAILED。