Protect upload cleanup and atomic completion paths, add durable media worker leases and brand asset validation, and introduce verified migration/bootstrap/deployment gates.
77 lines
2.4 KiB
TypeScript
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()
|
|
})
|
|
})
|