feat(health): local icon cache /api/icons/:slug.png (no CDN leak)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
31
tests/api/icons.test.js
Normal file
31
tests/api/icons.test.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import { createApp } from '../../server.js';
|
||||
import * as icons from '../../lib/health/icons.js';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
let app;
|
||||
const PNG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2, 3]);
|
||||
beforeAll(() => { app = createApp(); });
|
||||
beforeEach(() => { icons._setCacheDir(path.join(tmpdir(), 'void-icons-' + Date.now() + '-' + Math.random().toString(36).slice(2))); icons._setFetcher(null); });
|
||||
|
||||
describe('icon cache', () => {
|
||||
it('rejects an invalid slug', async () => {
|
||||
const res = await request(app).get('/api/icons/..%2f..%2fetc%2fpasswd.png');
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
it('fetches once on miss then serves from cache', async () => {
|
||||
const fetcher = vi.fn().mockResolvedValue(PNG);
|
||||
icons._setFetcher(fetcher);
|
||||
const r1 = await request(app).get('/api/icons/gitea.png');
|
||||
expect(r1.status).toBe(200);
|
||||
expect(r1.headers['content-type']).toContain('image/png');
|
||||
await request(app).get('/api/icons/gitea.png');
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('404s when upstream has no icon', async () => {
|
||||
icons._setFetcher(vi.fn().mockResolvedValue(null));
|
||||
expect((await request(app).get('/api/icons/nope.png')).status).toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user