feat: Category Icons

This commit is contained in:
OpenVelo Agent
2026-07-23 16:14:05 +00:00
parent e9020dfa23
commit d95367be1f
5 changed files with 398 additions and 69 deletions
@@ -0,0 +1,82 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import sharp from 'sharp';
import {
CANONICAL_SYSTEM_ICON_KEYS,
SYSTEM_ICON_SIZE,
seedSystemCategoryIcons,
} from '../../backend/src/database/system-category-icons';
function makeTempDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'hipctf-icons-'));
}
describe('seedSystemCategoryIcons', () => {
const cleanup: string[] = [];
afterEach(() => {
while (cleanup.length > 0) {
const dir = cleanup.pop();
if (dir && fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
}
});
it('writes six 128x128 PNGs into a fresh temp directory', async () => {
const dir = makeTempDir();
cleanup.push(dir);
const result = await seedSystemCategoryIcons(dir);
expect(result.written.length).toBe(CANONICAL_SYSTEM_ICON_KEYS.length);
expect(result.skipped.length).toBe(0);
for (const key of CANONICAL_SYSTEM_ICON_KEYS) {
const target = path.join(dir, 'icons', `${key}.png`);
expect(fs.existsSync(target)).toBe(true);
const stat = fs.statSync(target);
expect(stat.size).toBeGreaterThan(0);
const meta = await sharp(target).metadata();
expect(meta.format).toBe('png');
expect(meta.width).toBe(SYSTEM_ICON_SIZE);
expect(meta.height).toBe(SYSTEM_ICON_SIZE);
}
});
it('is idempotent and does not overwrite an existing admin-uploaded icon', async () => {
const dir = makeTempDir();
cleanup.push(dir);
const first = await seedSystemCategoryIcons(dir);
expect(first.written.length).toBe(CANONICAL_SYSTEM_ICON_KEYS.length);
const adminIconPath = path.join(dir, 'icons', 'CRY.png');
const adminMtime = fs.statSync(adminIconPath).mtimeMs;
await new Promise((r) => setTimeout(r, 25));
const second = await seedSystemCategoryIcons(dir);
expect(second.written.length).toBe(0);
expect(second.skipped.length).toBe(CANONICAL_SYSTEM_ICON_KEYS.length);
expect(fs.statSync(adminIconPath).mtimeMs).toBe(adminMtime);
for (const key of CANONICAL_SYSTEM_ICON_KEYS) {
const target = path.join(dir, 'icons', `${key}.png`);
expect(fs.statSync(target).size).toBeGreaterThan(0);
}
});
it('creates the icons subdirectory when the parent directory is empty', async () => {
const dir = makeTempDir();
cleanup.push(dir);
const target = path.join(dir, 'icons');
expect(fs.existsSync(target)).toBe(false);
const result = await seedSystemCategoryIcons(dir);
expect(result.written.length).toBe(CANONICAL_SYSTEM_ICON_KEYS.length);
expect(fs.existsSync(target)).toBe(true);
expect(fs.readdirSync(target).sort()).toEqual(
[...CANONICAL_SYSTEM_ICON_KEYS].map((k) => `${k}.png`).sort(),
);
});
});