68 lines
3.4 KiB
TypeScript
68 lines
3.4 KiB
TypeScript
import { test } from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { validate } from '../src/shared/validation';
|
||
import { RecordValue } from '../src/io/recordValue';
|
||
|
||
const ctx = {
|
||
hasSkill: (u: number) => [6001, 6301, 6501].includes(u),
|
||
hasField: (u: number) => [7015].includes(u),
|
||
hasHero: (u: number) => [5011, 6001].includes(u),
|
||
heroListKeys: new Set<string>(['5011']),
|
||
};
|
||
|
||
function heroObj(extra: Record<string, RecordValue> = {}): RecordValue {
|
||
return { kind: 'obj', props: {
|
||
uuid: { kind: 'num', value: 5011 }, name: { kind: 'str', value: 'x' }, path: { kind: 'str', value: 'p' },
|
||
fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' }, lv: { kind: 'num', value: 1 },
|
||
type: { kind: 'enumRef', qualifier: 'HType', member: 'Melee' }, hp: { kind: 'num', value: 400 },
|
||
ap: { kind: 'num', value: 20 },
|
||
skills: { kind: 'obj', props: { 6001: { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 }, lv: { kind: 'num', value: 1 }, cd: { kind: 'speed', level: 'Slow3' }, ccd: { kind: 'num', value: 0 } } } } },
|
||
info: { kind: 'str', value: 'ok' },
|
||
...extra,
|
||
} };
|
||
}
|
||
|
||
test('valid hero → no errors', () => {
|
||
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx);
|
||
assert.equal(issues.filter(i => i.severity === 'error').length, 0);
|
||
});
|
||
test('duplicate uuid → error', () => {
|
||
// 两条记录携带相同的 uuid 字段值(Map 键不同,uuid 字段相同)
|
||
const dup = heroObj(); // uuid=5011
|
||
const issues = validate('hero', new Map([['5011', heroObj()], ['5012', dup]]), ctx);
|
||
assert.ok(issues.some(i => i.severity === 'error' && i.code === 'dup-uuid'));
|
||
});
|
||
test('missing required field → error', () => {
|
||
const h = heroObj(); delete h.props['name'];
|
||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||
assert.ok(issues.some(i => i.code === 'missing-required' && i.fieldPath === 'name'));
|
||
});
|
||
test('trigger slot references missing skill → error', () => {
|
||
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
|
||
s_uuid: { kind: 'num', value: 9999 }, t_num: { kind: 'num', value: 3 } } }] } });
|
||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||
assert.ok(issues.some(i => i.code === 'dangling-ref'));
|
||
});
|
||
test('field list references missing field skill → error', () => {
|
||
const h = heroObj({ field: { kind: 'arr', items: [{ kind: 'num', value: 8888 }] } });
|
||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||
assert.ok(issues.some(i => i.code === 'dangling-ref'));
|
||
});
|
||
test('HeroList/hero consistency: fac=HERO entry missing from HeroList → error', () => {
|
||
const ctx2 = { ...ctx, heroListKeys: new Set<string>([]) };
|
||
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx2);
|
||
assert.ok(issues.some(i => i.code === 'herolist-inconsistent'));
|
||
});
|
||
test('invalid enum value → error', () => {
|
||
const h = heroObj({ fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'NOPE' } });
|
||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||
assert.ok(issues.some(i => i.code === 'bad-enum'));
|
||
});
|
||
test('unknown overrides key → error', () => {
|
||
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
|
||
s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 },
|
||
overrides: { kind: 'obj', props: { bogus: { kind: 'num', value: 1 } } } } }] } });
|
||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||
assert.ok(issues.some(i => i.code === 'bad-override-key'));
|
||
});
|