/** @jest-environment node */ export {} const sendMock = jest.fn() const headObjectCommandMock = jest.fn((input) => ({ input })) jest.mock('@aws-sdk/client-s3', () => ({ S3Client: jest.fn(() => ({ send: sendMock })), PutObjectCommand: jest.fn((input) => ({ input })), DeleteObjectCommand: jest.fn((input) => ({ input })), GetObjectCommand: jest.fn((input) => ({ input })), HeadObjectCommand: headObjectCommandMock, })) describe('verifyUploadedS3Object', () => { beforeEach(() => { jest.clearAllMocks() process.env.S3_BUCKET = 'gongxiangpan' process.env.S3_PUBLIC_DOMAIN = 'https://s3.jiansun.vip/gongxiangpan' process.env.S3_ENDPOINT = 'https://s3.jiansun.vip' process.env.S3_REGION = 'us-east-1' process.env.S3_ACCESS_KEY_ID = 'test-key' process.env.S3_SECRET_ACCESS_KEY = 'test-secret' process.env.S3_FILE_PATH = '由壹点AI/' }) test('使用受控 Bucket/Key 做 HEAD,并接受匹配的大小和类型', async () => { sendMock.mockResolvedValue({ ContentLength: 7975, ContentType: 'image/png; charset=binary', }) jest.resetModules() const { verifyUploadedS3Object } = await import('@/lib/s3') await verifyUploadedS3Object( 'https://s3.jiansun.vip/gongxiangpan/%E7%94%B1%E5%A3%B9%E7%82%B9AI/brand/header.png', 7975, 'image/png', ) expect(headObjectCommandMock).toHaveBeenCalledWith({ Bucket: 'gongxiangpan', Key: '由壹点AI/brand/header.png', }) expect(sendMock).toHaveBeenCalledTimes(1) }) test('对象大小不一致时拒绝通过', async () => { sendMock.mockResolvedValue({ ContentLength: 1, ContentType: 'image/png' }) jest.resetModules() const { verifyUploadedS3Object } = await import('@/lib/s3') await expect( verifyUploadedS3Object( 'https://s3.jiansun.vip/gongxiangpan/%E7%94%B1%E5%A3%B9%E7%82%B9AI/brand/header.png', 7975, 'image/png', ), ).rejects.toThrow('上传对象大小校验失败') }) test('对象类型不一致时拒绝通过', async () => { sendMock.mockResolvedValue({ ContentLength: 7975, ContentType: 'text/html' }) jest.resetModules() const { verifyUploadedS3Object } = await import('@/lib/s3') await expect( verifyUploadedS3Object( 'https://s3.jiansun.vip/gongxiangpan/%E7%94%B1%E5%A3%B9%E7%82%B9AI/brand/header.png', 7975, 'image/png', ), ).rejects.toThrow('上传对象类型校验失败') }) test.each([ 'https://attacker.example.com/gongxiangpan/由壹点AI/brand/header.png', 'https://s3.jiansun.vip/gongxiangpan/other-project/header.png', ])('非受控地址或业务前缀不会发出 HEAD: %s', async (url) => { jest.resetModules() const { verifyUploadedS3Object } = await import('@/lib/s3') await expect( verifyUploadedS3Object(url, 7975, 'image/png'), ).rejects.toThrow() expect(headObjectCommandMock).not.toHaveBeenCalled() expect(sendMock).not.toHaveBeenCalled() }) })