- Implemented `upsert-allewis-from-5001.js` to fetch and update Allewis data from API. - Created `upsert-missing-movement-from-5001.js` to fill missing movement and wounds data from API. - Developed `upsert-missing-movement-from-pdfs.js` to extract movement and wounds from PDF text. - Added `upsert-missing-profiles-from-pdfs.js` to fill missing profiles from PDF data. - Introduced `upsert-missing-wounds-from-pdfs.js` to update wounds data from PDF sources. - Created `validate-bestiary.js` to validate the structure of the bestiary JSON. - Updated `BestiaryTab.jsx` component to display bestiary data with improved structure and search functionality. - Added tests for `BestiaryTab` to ensure proper rendering and functionality.
95 lines
3.1 KiB
JavaScript
95 lines
3.1 KiB
JavaScript
// scripts/validate-bestiary.js
|
|
// Validate structure of database/deathwatch-bestiary-extracted.json
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const BESTIARY_PATH = path.join(__dirname, '../database/deathwatch-bestiary-extracted.json');
|
|
|
|
function isInt(n){return Number.isInteger(n);}
|
|
function isNonEmptyString(s){return typeof s === 'string' && s.trim().length>0}
|
|
|
|
function validateProfile(profile){
|
|
const keys = ['ws','bs','s','t','ag','int','per','wp','fel'];
|
|
if(!profile || typeof profile !== 'object') return {ok:false,errs:['missing profile object']};
|
|
const errs = [];
|
|
for(const k of keys){
|
|
const v = profile[k];
|
|
if(v===null || v===undefined) errs.push(`profile.${k} is missing`);
|
|
else if(!isInt(v)) errs.push(`profile.${k} is not integer (${v})`);
|
|
}
|
|
return {ok:errs.length===0, errs};
|
|
}
|
|
|
|
function validateMovement(mv){
|
|
if(mv===null || mv===undefined) return {ok:false,errs:['movement missing']};
|
|
if(typeof mv === 'string' && mv.match(/^\d+\/\d+\/\d+\/\d+$/)) return {ok:true,errs:[]};
|
|
return {ok:false,errs:[`movement has unexpected format: ${mv}`]};
|
|
}
|
|
|
|
function validateWounds(w){
|
|
if(w===null || w===undefined) return {ok:false,errs:['wounds missing']};
|
|
if(typeof w === 'number' && Number.isFinite(w)) return {ok:true,errs:[]};
|
|
if(typeof w === 'string' && /^\d+$/.test(w)) return {ok:true,errs:[]};
|
|
return {ok:false,errs:[`wounds invalid: ${w}`]};
|
|
}
|
|
|
|
function validateEntry(e){
|
|
const errs = [];
|
|
if(!isNonEmptyString(e.bestiaryName) && !isNonEmptyString(e.name)) errs.push('missing bestiaryName/name');
|
|
if(!isNonEmptyString(e.source) && !isNonEmptyString(e.book)) errs.push('missing source/book');
|
|
|
|
const stats = e.stats || {};
|
|
const profile = stats.profile || {};
|
|
const p = validateProfile(profile);
|
|
if(!p.ok) errs.push(...p.errs);
|
|
|
|
const mv = validateMovement(stats.movement);
|
|
if(!mv.ok) errs.push(...mv.errs);
|
|
|
|
const w = validateWounds(stats.wounds);
|
|
if(!w.ok) errs.push(...w.errs);
|
|
|
|
// weapons or gear recommended
|
|
if(!stats.weapons && !stats.gear) errs.push('neither weapons nor gear present');
|
|
|
|
return errs;
|
|
}
|
|
|
|
function run(){
|
|
if(!fs.existsSync(BESTIARY_PATH)){
|
|
console.error('Bestiary file not found at', BESTIARY_PATH);
|
|
process.exit(2);
|
|
}
|
|
let obj;
|
|
try{ obj = JSON.parse(fs.readFileSync(BESTIARY_PATH,'utf8')); } catch(e){ console.error('Failed to parse bestiary JSON:', e.message); process.exit(2); }
|
|
const arr = Array.isArray(obj) ? obj : (Array.isArray(obj.results) ? obj.results : []);
|
|
if(!Array.isArray(arr)){
|
|
console.error('Bestiary format unrecognized');
|
|
process.exit(2);
|
|
}
|
|
|
|
let failures=0;
|
|
const report = [];
|
|
arr.forEach((entry, idx)=>{
|
|
const errs = validateEntry(entry);
|
|
if(errs.length){
|
|
failures++;
|
|
report.push({index: idx, name: entry.bestiaryName || entry.name || '(unknown)', errors: errs});
|
|
}
|
|
});
|
|
|
|
console.log(`Entries checked: ${arr.length}. Failures: ${failures}`);
|
|
if(report.length){
|
|
console.log('Failures detail:');
|
|
for(const r of report){
|
|
console.log(`- [${r.index}] ${r.name}:`);
|
|
for(const e of r.errors) console.log(' -', e);
|
|
}
|
|
}
|
|
|
|
process.exit(failures>0?1:0);
|
|
}
|
|
|
|
run();
|