Barazo AppView backend barazo.forum
at main 114 lines 4.1 kB view raw
1import { describe, it, expect, afterEach, vi } from 'vitest' 2import { existsSync } from 'node:fs' 3import { readFile, rm, mkdtemp } from 'node:fs/promises' 4import { join } from 'node:path' 5import { tmpdir } from 'node:os' 6import { createLocalStorage } from '../../../src/lib/storage.js' 7 8// --------------------------------------------------------------------------- 9// Mock logger 10// --------------------------------------------------------------------------- 11 12const mockLogger = { 13 debug: vi.fn(), 14 info: vi.fn(), 15 warn: vi.fn(), 16 error: vi.fn(), 17 fatal: vi.fn(), 18 trace: vi.fn(), 19 child: vi.fn(), 20 silent: vi.fn(), 21 level: 'debug', 22} as never 23 24// --------------------------------------------------------------------------- 25// Tests 26// --------------------------------------------------------------------------- 27 28describe('createLocalStorage', () => { 29 let tmpDir: string 30 31 afterEach(async () => { 32 if (tmpDir && existsSync(tmpDir)) { 33 await rm(tmpDir, { recursive: true, force: true }) 34 } 35 }) 36 37 it('stores a file and returns a valid URL', async () => { 38 tmpDir = await mkdtemp(join(tmpdir(), 'barazo-storage-')) 39 const storage = createLocalStorage(tmpDir, 'http://localhost:3000', mockLogger) 40 41 const data = Buffer.from('fake-image-data') 42 const url = await storage.store(data, 'image/webp', 'avatars') 43 44 expect(url).toMatch(/^http:\/\/localhost:3000\/uploads\/avatars\/avatars-[a-f0-9-]+\.webp$/) 45 46 // Verify the file was actually written 47 const relativePath = url.split('/uploads/')[1] 48 expect(relativePath).toBeDefined() 49 const filepath = join(tmpDir, relativePath ?? '') 50 const written = await readFile(filepath) 51 expect(written.toString()).toBe('fake-image-data') 52 }) 53 54 it('creates subdirectory if it does not exist', async () => { 55 tmpDir = await mkdtemp(join(tmpdir(), 'barazo-storage-')) 56 const storage = createLocalStorage(tmpDir, 'http://localhost:3000', mockLogger) 57 58 const data = Buffer.from('test') 59 await storage.store(data, 'image/png', 'banners') 60 61 expect(existsSync(join(tmpDir, 'banners'))).toBe(true) 62 }) 63 64 it('maps MIME types to correct extensions', async () => { 65 tmpDir = await mkdtemp(join(tmpdir(), 'barazo-storage-')) 66 const storage = createLocalStorage(tmpDir, 'http://localhost:3000', mockLogger) 67 68 const data = Buffer.from('test') 69 70 const jpegUrl = await storage.store(data, 'image/jpeg', 'test') 71 expect(jpegUrl).toMatch(/\.jpg$/) 72 73 const pngUrl = await storage.store(data, 'image/png', 'test') 74 expect(pngUrl).toMatch(/\.png$/) 75 76 const gifUrl = await storage.store(data, 'image/gif', 'test') 77 expect(gifUrl).toMatch(/\.gif$/) 78 79 const unknownUrl = await storage.store(data, 'application/octet-stream', 'test') 80 expect(unknownUrl).toMatch(/\.bin$/) 81 }) 82 83 it('deletes a stored file', async () => { 84 tmpDir = await mkdtemp(join(tmpdir(), 'barazo-storage-')) 85 const storage = createLocalStorage(tmpDir, 'http://localhost:3000', mockLogger) 86 87 const data = Buffer.from('to-be-deleted') 88 const url = await storage.store(data, 'image/webp', 'avatars') 89 90 // File exists before delete 91 const relativePath = url.split('/uploads/')[1] ?? '' 92 const filepath = join(tmpDir, relativePath) 93 expect(existsSync(filepath)).toBe(true) 94 95 await storage.delete(url) 96 expect(existsSync(filepath)).toBe(false) 97 }) 98 99 it('delete is best-effort (does not throw for missing files)', async () => { 100 tmpDir = await mkdtemp(join(tmpdir(), 'barazo-storage-')) 101 const storage = createLocalStorage(tmpDir, 'http://localhost:3000', mockLogger) 102 103 // Should not throw 104 await storage.delete('http://localhost:3000/uploads/avatars/nonexistent.webp') 105 }) 106 107 it('delete ignores URLs without /uploads/ path', async () => { 108 tmpDir = await mkdtemp(join(tmpdir(), 'barazo-storage-')) 109 const storage = createLocalStorage(tmpDir, 'http://localhost:3000', mockLogger) 110 111 // Should not throw and should not attempt file deletion 112 await storage.delete('http://example.com/some-other-path.jpg') 113 }) 114})