/** @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() }) })