import { describe, it, expect } from 'vitest'; import AdmZip from 'adm-zip'; import { processFile, unpackZip, fetchUrl, MAX_FILE } from '../../lib/icons/ingest.js'; const PNG = Buffer.from([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a, 0,0,0,0]); describe('processFile', () => { it('slugifies name, keeps png', () => { const r = processFile({ name: 'My Router.png', buffer: PNG }); expect(r.name).toBe('my-router.png'); expect(r.buffer).toBe(PNG); }); it('sanitizes svg', () => { const r = processFile({ name: 'x.svg', buffer: Buffer.from('') }); expect(r.buffer.toString()).not.toMatch(/script/i); }); it('rejects non-image extension', () => { expect(() => processFile({ name: 'x.exe', buffer: PNG })).toThrow(); }); it('rejects oversize', () => { expect(() => processFile({ name: 'x.png', buffer: Buffer.alloc(MAX_FILE + 1, 1) })).toThrow(); }); it('rejects png with bad magic', () => { expect(() => processFile({ name: 'x.png', buffer: Buffer.from('not a png') })).toThrow(); }); }); describe('unpackZip', () => { it('extracts images, skips non-image junk', () => { const z = new AdmZip(); z.addFile('a.png', PNG); z.addFile('notes.txt', Buffer.from('hi')); const out = unpackZip(z.toBuffer()); expect(out.map(f => f.name)).toEqual(['a.png']); }); it('skips path-traversal entries', () => { // adm-zip's addFile() sanitizes '../' at write time (zipnamefix), so it // can't produce a real traversal entry. Build one by mutating the entry // name at the raw level *after* addFile — this survives serialization and // stores '../evil.png' verbatim in the zip bytes. const z = new AdmZip(); z.addFile('a.png', PNG); z.addFile('placeholder.png', PNG); const entries = z.getEntries(); entries[1].entryName = '../evil.png'; const buf = z.toBuffer(); // Sanity check: the traversal entry name really is in the serialized bytes. expect(buf.includes(Buffer.from('../evil.png'))).toBe(true); const out = unpackZip(buf); expect(out.map(f => f.name)).toEqual(['a.png']); }); }); describe('fetchUrl', () => { it('rejects non-http schemes', async () => { await expect(fetchUrl('file:///etc/passwd')).rejects.toThrow(); }); it('rejects localhost/private hosts', async () => { await expect(fetchUrl('http://127.0.0.1/x.png')).rejects.toThrow(); }); it('fetches via injected fetcher', async () => { const fake = async () => ({ ok: true, arrayBuffer: async () => PNG.buffer.slice(PNG.byteOffset, PNG.byteOffset + PNG.length), headers: new Map([['content-type','image/png']]) }); const r = await fetchUrl('https://example.com/x.png', { fetcher: fake }); expect(Buffer.isBuffer(r.buffer)).toBe(true); }); });