54 lines
3.3 KiB
JavaScript
54 lines
3.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { gzipSync, gunzipSync } from 'node:zlib';
|
|
import { openGameDb, readGameDbText, prepareGameDbSource, sha256 } from './game-db-source.mjs';
|
|
|
|
test('snapshot reader rejects tampering, path traversal and missing tables', () => {
|
|
const root = mkdtempSync(path.join(tmpdir(), 'holocalc-db-test-'));
|
|
try {
|
|
const version = 'a'.repeat(64), snapshot = `snapshots/${version}`;
|
|
const dir = path.join(root, snapshot); mkdirSync(dir, { recursive: true });
|
|
const raw = Buffer.from('[{"data":{"id":"test"}}]'), packed = gzipSync(raw);
|
|
const manifest = JSON.stringify({ schemaVersion: 1, kind: 'android-master-db', locale: 'kor',
|
|
masterDataVersion: version, tables: { Card: { file: 'Card.json.gz', rows: 1,
|
|
sha256: sha256(raw), gzipSha256: sha256(packed) } } });
|
|
writeFileSync(path.join(dir, 'manifest.json'), manifest);
|
|
writeFileSync(path.join(dir, 'Card.json.gz'), packed);
|
|
const pointer = { snapshot, manifestSha256: sha256(manifest) };
|
|
const source = openGameDb(pointer, root);
|
|
assert.equal(JSON.parse(readGameDbText(source, 'Card.json'))[0].data.id, 'test');
|
|
assert.throws(() => readGameDbText(source, 'Missing.json'), /Missing game DB table/);
|
|
assert.throws(() => openGameDb({ ...pointer, snapshot: '../outside' }, root), /Invalid/);
|
|
assert.throws(() => openGameDb({ ...pointer, manifestSha256: 'bad' }, root), /hash mismatch/);
|
|
writeFileSync(path.join(dir, 'Card.json.gz'), gzipSync('[]'));
|
|
assert.throws(() => readGameDbText(source, 'Card.json'), /hash mismatch/);
|
|
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('every captured table verifies, and unsupported locales cannot use external fallback', () => {
|
|
const source = prepareGameDbSource('kor', { review: true });
|
|
assert.ok(Object.keys(source.tables).length > 0);
|
|
let rows = 0;
|
|
for (const name of Object.keys(source.tables)) rows += JSON.parse(readGameDbText(source, name + '.json')).length;
|
|
assert.ok(rows > 0);
|
|
assert.throws(() => prepareGameDbSource('eng'), /no external fallback/);
|
|
const cards = JSON.parse(readFileSync(new URL('../src/lib/data/cards-summary.json', import.meta.url)));
|
|
assert.equal(cards.masterDataVersion, source.masterDataVersion);
|
|
assert.equal(cards.cards.length, JSON.parse(readGameDbText(source, 'Card.json')).length);
|
|
});
|
|
|
|
// Independent game assets pin the two corrections to the old community reference table.
|
|
test('m0337 game SUS supplies the corrected denominator and matches service provenance', async () => {
|
|
const { susScoreWeight } = await import('./sus-score-weight.mjs');
|
|
for (const [difficulty, expected] of [['hard', 539500], ['expert', 884700]]) {
|
|
const raw = gunzipSync(readFileSync(new URL('./fixtures/game-sus/m0337.' + difficulty + '.sus.gz', import.meta.url)));
|
|
const chart = JSON.parse(readFileSync(new URL('../static/data/charts/m0337.' + difficulty + '.json', import.meta.url)));
|
|
assert.equal(susScoreWeight(raw.toString('utf8')).perfectNoteWeight, expected);
|
|
assert.equal(chart.source.susSha256, sha256(raw));
|
|
assert.equal(chart.scoreFormula.perfectNoteWeight, expected);
|
|
}
|
|
});
|