gongxiangp2/lib/__tests__/s3-delete.test.ts
chenpei 7326e273e1 fix: harden media upload and release safety
Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
2026-08-12 18:04:08 +08:00

77 lines
2.4 KiB
TypeScript

/** @jest-environment node */
export {}
const sendMock = jest.fn()
const deleteObjectCommandMock = jest.fn((input) => ({ input }))
jest.mock('@aws-sdk/client-s3', () => ({
S3Client: jest.fn(() => ({
send: sendMock,
})),
PutObjectCommand: jest.fn((input) => ({ input })),
DeleteObjectCommand: deleteObjectCommandMock,
GetObjectCommand: jest.fn((input) => ({ input })),
}))
describe('deleteFromS3', () => {
const originalS3FilePath = process.env.S3_FILE_PATH
beforeEach(() => {
jest.clearAllMocks()
process.env.S3_BUCKET = 'jsxiangche'
process.env.S3_PUBLIC_DOMAIN = 'https://s3.1080p.hk/jsxiangche'
process.env.S3_ENDPOINT = 'https://s3.1080p.hk'
process.env.S3_REGION = 'us-east-1'
process.env.S3_ACCESS_KEY_ID = 'test-key'
process.env.S3_SECRET_ACCESS_KEY = 'test-secret'
delete process.env.S3_FILE_PATH
})
afterAll(() => {
if (originalS3FilePath === undefined) {
delete process.env.S3_FILE_PATH
} else {
process.env.S3_FILE_PATH = originalS3FilePath
}
})
test('删除公网 URL 时应解码中文路径并去掉 bucket 前缀', async () => {
jest.resetModules()
const { deleteFromS3 } = await import('@/lib/s3')
await deleteFromS3(
'https://s3.1080p.hk/jsxiangche/%E7%94%B1%E5%A3%B9%E7%82%B9AI/external-material-import-real-test/test-image.png',
)
expect(deleteObjectCommandMock).toHaveBeenCalledWith({
Bucket: 'jsxiangche',
Key: '由壹点AI/external-material-import-real-test/test-image.png',
})
expect(sendMock).toHaveBeenCalledTimes(1)
})
test('拒绝把任意 HTTP 域名解释为当前存储桶对象', async () => {
jest.resetModules()
const { deleteFromS3 } = await import('@/lib/s3')
await expect(
deleteFromS3('https://attacker.example.com/jsxiangche/other-user/file.png'),
).rejects.toThrow('拒绝不受信任的对象存储地址')
expect(deleteObjectCommandMock).not.toHaveBeenCalled()
expect(sendMock).not.toHaveBeenCalled()
})
test('配置业务前缀后拒绝删除前缀之外的对象', async () => {
process.env.S3_FILE_PATH = '由壹点AI/'
jest.resetModules()
const { deleteFromS3 } = await import('@/lib/s3')
await expect(
deleteFromS3('https://s3.1080p.hk/jsxiangche/other-user/file.png'),
).rejects.toThrow('拒绝删除当前业务前缀之外的对象')
expect(deleteObjectCommandMock).not.toHaveBeenCalled()
expect(sendMock).not.toHaveBeenCalled()
})
})