/**
* ─────────────────────────────────────────────
* SPB Git — Personal Git Platform
* ─────────────────────────────────────────────
* Author : Simon-Pierre Boucher
* Contact : contact@spboucher.ai
* File : test/unit/util.test.mjs
* Purpose : Unit tests — validation, path safety, formatting
* License : MIT © Simon-Pierre Boucher
* ─────────────────────────────────────────────
*/
import { describe, it, expect } from 'vitest';
import {
isValidRepoName, safeJoin, isValidTreePath, formatBytes, escapeHtml, identiconSvg, mapLimit,
} from '../../src/lib/util.mjs';
describe('isValidRepoName', () => {
it('accepts sane names', () => {
for (const name of ['demo', 'api-tools', 'a', 'repo.name', 'x_1', 'a2.b-c_d']) {
expect(isValidRepoName(name), name).toBe(true);
}
});
it('rejects traversal, uppercase, and junk', () => {
for (const name of ['../etc', 'a/../b', 'A', '-lead', '.hidden', '', 'a'.repeat(65), 'name.git', 'a..b', 'a b']) {
expect(isValidRepoName(name), String(name)).toBe(false);
}
});
});
describe('safeJoin', () => {
it('keeps paths inside the root', () => {
expect(safeJoin('/srv/git', 'demo.git')).toBe('/srv/git/demo.git');
});
it('throws on escape attempts', () => {
expect(() => safeJoin('/srv/git', '../../etc/passwd')).toThrow();
expect(() => safeJoin('/srv/git', 'a', '..', '..', 'b')).toThrow();
});
});
describe('isValidTreePath', () => {
it('accepts normal repo paths', () => {
expect(isValidTreePath('src/index.js')).toBe(true);
expect(isValidTreePath('a.txt')).toBe(true);
});
it('rejects traversal and absolutes', () => {
for (const path of ['../x', 'a/../b', '/abs', 'a//b', 'a/./b', 'nul\0byte']) {
expect(isValidTreePath(path), path).toBe(false);
}
});
});
describe('formatBytes', () => {
it('formats units', () => {
expect(formatBytes(0)).toBe('0 B');
expect(formatBytes(1024)).toBe('1.0 KB');
expect(formatBytes(1536)).toBe('1.5 KB');
expect(formatBytes(5 * 1024 * 1024)).toBe('5.0 MB');
});
});
describe('escapeHtml', () => {
it('escapes the five metacharacters', () => {
expect(escapeHtml('&\'')).toBe('<a href="x">&'</a>');
});
});
describe('identiconSvg', () => {
it('is deterministic and symmetric-sized', () => {
const a = identiconSvg('contact@spboucher.ai');
const b = identiconSvg('contact@spboucher.ai');
const c = identiconSvg('other@example.com');
expect(a).toBe(b);
expect(a).not.toBe(c);
expect(a).toContain('