Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+52 -14
View File
@@ -3,24 +3,51 @@ import { mkdtemp, mkdir, readFile, readdir, rm, symlink } from 'node:fs/promises
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { assertRegularPath, manifestChecksum, parseByteRange, receiveGameFile, validateManifest, validateGamePath, verifyGameFile } from '../../src/lib/server/game-files/storage.js';
import {
assertRegularPath,
manifestChecksum,
parseByteRange,
receiveGameFile,
validateManifest,
validateGamePath,
verifyGameFile
} from '../../src/lib/server/game-files/storage.js';
const sha = (data: string) => createHash('sha256').update(data).digest('hex');
const entry = (path = 'zone/file.ff', data = 'game data') => ({ path, size: Buffer.byteLength(data), sha256: sha(data) });
const entry = (path = 'zone/file.ff', data = 'game data') => ({
path,
size: Buffer.byteLength(data),
sha256: sha(data)
});
const stream = (value: string) => new Response(value).body!;
let directory: string;
afterEach(async () => { if (directory) await rm(directory, { recursive: true, force: true }); });
afterEach(async () => {
if (directory) await rm(directory, { recursive: true, force: true });
});
describe('game-file manifests', () => {
it.each(['../escape', '/absolute', 'a/../b', 'a//b', 'a\\b', 'C:/file', './file', 'a\0b', 'a/file.', 'a/file '])('rejects unsafe or ambiguous path %s', path => {
it.each([
'../escape',
'/absolute',
'a/../b',
'a//b',
'a\\b',
'C:/file',
'./file',
'a\0b',
'a/file.',
'a/file '
])('rejects unsafe or ambiguous path %s', (path) => {
expect(() => validateGamePath(path)).toThrow();
});
it('canonicalizes file order, rejects duplicates, and hashes the canonical manifest', () => {
const files = validateManifest([entry('z.ff'), entry('a.ff')]);
expect(files.map(file => file.path)).toEqual(['a.ff', 'z.ff']);
expect(files.map((file) => file.path)).toEqual(['a.ff', 'z.ff']);
expect(manifestChecksum(files)).toBe(sha(JSON.stringify(files)));
expect(() => validateManifest([entry('A.ff'), entry('a.ff')])).toThrow(/Duplicate/);
expect(() => validateManifest([entry('zone'), entry('zone/file.ff')])).toThrow(/file and a directory/);
expect(() => validateManifest([entry('zone'), entry('zone/file.ff')])).toThrow(
/file and a directory/
);
expect(() => validateManifest([{ ...entry(), size: -1 }])).toThrow();
expect(() => validateManifest([{ ...entry(), sha256: 'bad' }])).toThrow();
});
@@ -31,18 +58,26 @@ describe('streaming storage integrity', () => {
const file = entry();
await expect(receiveGameFile(directory, file, stream('bad'))).rejects.toThrow(/mismatch/);
expect(await readdir(join(directory, 'zone'))).toEqual([]);
await expect(receiveGameFile(directory, file, stream('game data too long'))).rejects.toThrow(/exceeds/);
await expect(
receiveGameFile(directory, file, stream('game data too long'))
).rejects.toThrow(/exceeds/);
await receiveGameFile(directory, file, stream('game data'));
expect(await readFile(join(directory, file.path), 'utf8')).toBe('game data');
await verifyGameFile(directory, file);
await expect(verifyGameFile(directory, { ...file, sha256: sha('something') })).rejects.toThrow(/Checksum/);
await expect(
verifyGameFile(directory, { ...file, sha256: sha('something') })
).rejects.toThrow(/Checksum/);
});
it('refuses symlinks before writing or reading shared files', async () => {
directory = await mkdtemp(join(tmpdir(), 'ec-game-symlink-'));
const root = join(directory, 'root'); await mkdir(root);
const outside = join(directory, 'outside'); await mkdir(outside);
const root = join(directory, 'root');
await mkdir(root);
const outside = join(directory, 'outside');
await mkdir(outside);
await symlink(outside, join(root, 'zone'));
await expect(receiveGameFile(root, entry(), stream('game data'))).rejects.toThrow(/non-regular/);
await expect(receiveGameFile(root, entry(), stream('game data'))).rejects.toThrow(
/non-regular/
);
await expect(assertRegularPath(root, 'zone/file.ff', true)).rejects.toThrow(/non-regular/);
expect(await readdir(outside)).toEqual([]);
});
@@ -55,7 +90,10 @@ describe('HTTP download resume ranges', () => {
expect(parseByteRange('bytes=-3', 10)).toEqual({ start: 7, end: 9 });
expect(parseByteRange('bytes=3-99', 10)).toEqual({ start: 3, end: 9 });
});
it.each(['bytes=10-', 'bytes=-0', 'bytes=5-3', 'bytes=0-1,3-4', 'bytes=-', 'not-a-range'])('rejects unsatisfiable %s', range => {
expect(() => parseByteRange(range, 10)).toThrow();
});
it.each(['bytes=10-', 'bytes=-0', 'bytes=5-3', 'bytes=0-1,3-4', 'bytes=-', 'not-a-range'])(
'rejects unsatisfiable %s',
(range) => {
expect(() => parseByteRange(range, 10)).toThrow();
}
);
});