Barazo AppView backend
barazo.forum
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})