Files
ecommerce-platform/test/unit/lib/storage.test.ts
T
davide 5428eeccc1 test: add unit tests for lib/ (validate, auth, storage, email, rate-limit)
49 tests for all 10 Zod schemas in validate.ts, 26 tests for auth (hashPassword,
verifyPassword, createSession, getSession, getCurrentUser, deleteSession), 11 for
storage (magic-byte validation, saveImage, deleteImageFile), 9 for email (sendMail
scenarios), and 6 for rate limiting logic.
2026-05-19 14:07:38 +02:00

127 lines
4.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
const { mockMkdir, mockWriteFile, mockUnlink } = vi.hoisted(() => ({
mockMkdir: vi.fn().mockResolvedValue(undefined),
mockWriteFile: vi.fn().mockResolvedValue(undefined),
mockUnlink: vi.fn().mockResolvedValue(undefined),
}))
vi.mock('fs/promises', () => ({
__esModule: true,
default: { mkdir: mockMkdir, writeFile: mockWriteFile, unlink: mockUnlink },
mkdir: mockMkdir,
writeFile: mockWriteFile,
unlink: mockUnlink,
}))
import { saveImage, validateImageMagicBytes, deleteImageFile } from '@/lib/storage'
function makeJpegBuffer(): Buffer {
const b = Buffer.alloc(12)
b[0] = 0xff; b[1] = 0xd8; b[2] = 0xff
return b
}
function makePngBuffer(): Buffer {
const b = Buffer.alloc(12)
b[0] = 0x89; b[1] = 0x50; b[2] = 0x4e; b[3] = 0x47
return b
}
function makeWebpBuffer(): Buffer {
const b = Buffer.alloc(12)
b[0] = 0x52; b[1] = 0x49; b[2] = 0x46; b[3] = 0x46
b[8] = 0x57; b[9] = 0x45; b[10] = 0x42; b[11] = 0x50
return b
}
function makeIcoBuffer(): Buffer {
const b = Buffer.alloc(12)
b[0] = 0x00; b[1] = 0x00; b[2] = 0x01; b[3] = 0x00
return b
}
function makeFileFromBuffer(buf: Buffer, type: string): File {
return new File([new Uint8Array(buf)], 'test.img', { type })
}
beforeEach(() => {
vi.clearAllMocks()
mockMkdir.mockResolvedValue(undefined)
mockWriteFile.mockResolvedValue(undefined)
mockUnlink.mockResolvedValue(undefined)
})
// ─── validateImageMagicBytes ──────────────────────────────────────────────────
describe('validateImageMagicBytes', () => {
it('accepts a valid JPEG', async () => {
const file = makeFileFromBuffer(makeJpegBuffer(), 'image/jpeg')
expect(await validateImageMagicBytes(file, 'image/jpeg')).toBe(true)
})
it('accepts a valid PNG', async () => {
const file = makeFileFromBuffer(makePngBuffer(), 'image/png')
expect(await validateImageMagicBytes(file, 'image/png')).toBe(true)
})
it('accepts a valid WebP', async () => {
const file = makeFileFromBuffer(makeWebpBuffer(), 'image/webp')
expect(await validateImageMagicBytes(file, 'image/webp')).toBe(true)
})
it('accepts a valid ICO', async () => {
const file = makeFileFromBuffer(makeIcoBuffer(), 'image/x-icon')
expect(await validateImageMagicBytes(file, 'image/x-icon')).toBe(true)
})
it('rejects JPEG buffer declared as PNG', async () => {
const file = makeFileFromBuffer(makeJpegBuffer(), 'image/png')
expect(await validateImageMagicBytes(file, 'image/png')).toBe(false)
})
it('rejects unknown MIME type', async () => {
const file = makeFileFromBuffer(makeJpegBuffer(), 'image/bmp')
expect(await validateImageMagicBytes(file, 'image/bmp')).toBe(false)
})
it('rejects a random buffer as JPEG', async () => {
const file = makeFileFromBuffer(Buffer.from([0x00, 0x01, 0x02]), 'image/jpeg')
expect(await validateImageMagicBytes(file, 'image/jpeg')).toBe(false)
})
})
// ─── saveImage ────────────────────────────────────────────────────────────────
describe('saveImage', () => {
it('creates the product directory and writes file', async () => {
const buf = Buffer.from('fakeimage')
const url = await saveImage('prod-123', buf, 'photo.jpg')
expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('prod-123'), { recursive: true })
expect(mockWriteFile).toHaveBeenCalled()
expect(url).toMatch(/^\/uploads\/prod-123\//)
expect(url).toMatch(/photo\.jpg$/)
})
it('sanitizes filename (removes special chars)', async () => {
const buf = Buffer.from('fakeimage')
const url = await saveImage('prod-123', buf, 'my photo (1).JPG')
expect(url).toMatch(/my_photo__1_\.jpg$/)
})
it('returns a URL starting with /uploads/', async () => {
const url = await saveImage('prod-abc', Buffer.from('x'), 'img.png')
expect(url).toMatch(/^\/uploads\/prod-abc\//)
})
})
// ─── deleteImageFile ──────────────────────────────────────────────────────────
describe('deleteImageFile', () => {
it('calls unlink with the correct path', async () => {
await deleteImageFile('/uploads/prod-1/image.jpg')
expect(mockUnlink).toHaveBeenCalledWith(expect.stringContaining('/uploads/prod-1/image.jpg'))
})
})