import test from 'node:test' import assert from 'node:assert/strict' import os from 'node:os' import path from 'node:path' import { mkdtempSync, existsSync } from 'node:fs' import { readFile, writeFile } from 'node:fs/promises' import sharp from 'sharp' process.env.NEWS_UPLOAD_DIR = mkdtempSync(path.join(os.tmpdir(), 'news-images-')) const { storeNewsImage, claimNewsImage, deleteNewsImage, newsImagePath, purgeUnclaimedNewsImages, newsImageRateLimited, NEWS_IMAGE_MAX_BYTES } = await import('../server/services/newsImages.js') const solid = (width, height) => sharp({ create: { width, height, channels: 3, background: '#c33' } }) test('exif_gps_entfernt_und_gedreht', async () => { const input = await solid(1200, 800) .withMetadata({ orientation: 6 }) .withExif({ IFD3: { GPSLatitudeRef: 'N', GPSLatitude: '48/1 30/1 0/1' } }) .jpeg().toBuffer() const inMeta = await sharp(input).metadata() assert.ok(inMeta.exif, 'Fixture hat EXIF') assert.equal(inMeta.orientation, 6) const { imageId } = await storeNewsImage({ customerId: 'c1', projectId: 'p1', buffer: input }) for (const size of ['l', 's']) { const meta = await sharp(newsImagePath('p1', imageId, size)).metadata() assert.equal(meta.exif, undefined) assert.ok(meta.width < meta.height, `${size}: gedreht`) } }) test('verkleinert_auf_1600_und_480', async () => { const input = await solid(4000, 3000).png().toBuffer() const { imageId, w, h } = await storeNewsImage({ customerId: 'c1', projectId: 'p1', buffer: input }) assert.deepEqual([w, h], [1600, 1200]) const l = await sharp(newsImagePath('p1', imageId, 'l')).metadata() const s = await sharp(newsImagePath('p1', imageId, 's')).metadata() assert.deepEqual([l.format, l.width, l.height], ['webp', 1600, 1200]) assert.deepEqual([s.format, s.width, s.height], ['webp', 480, 360]) }) test('zu_gross_413', async () => { await assert.rejects( storeNewsImage({ customerId: 'c1', projectId: 'p1', buffer: Buffer.alloc(NEWS_IMAGE_MAX_BYTES + 1) }), { status: 413 }, ) }) test('falscher_typ_415', async () => { await assert.rejects( storeNewsImage({ customerId: 'c1', projectId: 'p1', buffer: Buffer.from('%PDF-1.4\n1 0 obj\n<<>>\nendobj\ntrailer\n<<>>\n%%EOF\n') }), { status: 415 }, ) }) test('pfad_nur_saubere_ids', () => { assert.equal(newsImagePath('p1', '../x', 'l'), null) assert.equal(newsImagePath('../p', 'abc-1', 'l'), null) assert.ok(newsImagePath('p1', 'abc-1', 's').endsWith(path.join('p1', 'abc-1-s.webp'))) }) test('unbenutzte_nach_24h_weg', async () => { const input = await solid(100, 100).png().toBuffer() const old = await storeNewsImage({ customerId: 'c1', projectId: 'p-purge', buffer: input }) const kept = await storeNewsImage({ customerId: 'c1', projectId: 'p-purge', buffer: input }) const fresh = await storeNewsImage({ customerId: 'c1', projectId: 'p-purge', buffer: input }) await claimNewsImage('p-purge', kept.imageId) const backdate = async (id) => { const f = path.join(process.env.NEWS_UPLOAD_DIR, 'p-purge', `${id}.json`) const meta = JSON.parse(await readFile(f, 'utf8')) meta.createdAt = new Date(Date.now() - 25 * 3600 * 1000).toISOString() await writeFile(f, JSON.stringify(meta)) } await backdate(old.imageId) await backdate(kept.imageId) assert.equal(await purgeUnclaimedNewsImages(), 1) assert.equal(existsSync(newsImagePath('p-purge', old.imageId, 'l')), false) assert.equal(existsSync(newsImagePath('p-purge', old.imageId, 's')), false) assert.equal(existsSync(newsImagePath('p-purge', kept.imageId, 'l')), true) assert.equal(existsSync(newsImagePath('p-purge', fresh.imageId, 'l')), true) }) test('zu_viele_pixel_415', async () => { const input = await solid(8000, 8000).png({ compressionLevel: 9 }).toBuffer() assert.ok(input.length < NEWS_IMAGE_MAX_BYTES) await assert.rejects(storeNewsImage({ customerId: 'c1', projectId: 'p1', buffer: input }), { status: 415 }) }) test('kaputtes_bild_415', async () => { const input = (await solid(100, 100).png().toBuffer()).subarray(0, 60) await assert.rejects(storeNewsImage({ customerId: 'c1', projectId: 'p1', buffer: input }), { status: 415 }) }) test('loeschen_entfernt_dateien_und_sidecar', async () => { const input = await solid(100, 100).png().toBuffer() const { imageId } = await storeNewsImage({ customerId: 'c1', projectId: 'p-del', buffer: input }) const sidecar = path.join(process.env.NEWS_UPLOAD_DIR, 'p-del', `${imageId}.json`) assert.equal(existsSync(sidecar), true) await deleteNewsImage('p-del', imageId) assert.equal(existsSync(newsImagePath('p-del', imageId, 'l')), false) assert.equal(existsSync(newsImagePath('p-del', imageId, 's')), false) assert.equal(existsSync(sidecar), false) await deleteNewsImage('p-del', imageId) }) test('rate_limit_20_pro_stunde', () => { for (let i = 0; i < 20; i++) assert.equal(newsImageRateLimited('rl'), false) assert.equal(newsImageRateLimited('rl'), true) assert.equal(newsImageRateLimited('other'), false) })