Client/GameServer/tests/run.js
2026-03-10 13:06:33 -03:00

141 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* GSO Test Runner (no external dependencies)
* Run: node tests/run.js
*/
// Mock mongoose so MarketSystem can be required without a live DB
const Module = require('module');
const origLoad = Module._load;
Module._load = function(name, ...args) {
if (name === 'mongoose') {
const fakeSchema = function(def) { this.def = def; };
fakeSchema.prototype.index = function() {};
fakeSchema.Types = { Mixed: 'Mixed' };
return { Schema: fakeSchema, model: () => class {} };
}
return origLoad.call(this, name, ...args);
};
let passed = 0, failed = 0;
function test(name, fn) {
try { fn(); console.log(`${name}`); passed++; }
catch(e) { console.log(`${name}\n ${e.message}`); failed++; }
}
function expect(val) {
return {
toBe: (x) => { if (val !== x) throw new Error(`Expected ${JSON.stringify(x)}, got ${JSON.stringify(val)}`); },
toBeTruthy: () => { if (!val) throw new Error(`Expected truthy, got ${val}`); },
toBeGreaterThan:(x) => { if (!(val > x)) throw new Error(`Expected > ${x}, got ${val}`); },
toBeLessThan: (x) => { if (!(val < x)) throw new Error(`Expected < ${x}, got ${val}`); },
};
}
// ─── CraftingSystem ──────────────────────────────────────────────────────────
console.log('\n📦 CraftingSystem');
const CraftingSystem = require('../systems/CraftingSystem');
const mockLoader = {
getRecipe: (id) => id === 'test_recipe' ? {
recipe: { inputs: { iron_ore: 2 }, output: { iron_ingot: 1 }, craft_time_seconds: 30 },
craft: { xp: 10 }
} : null,
getAllRecipes: () => [], getRecipesByType: () => [], getCraftingTabs: () => [],
};
const cs = new CraftingSystem(mockLoader);
test('canCraft with sufficient items', () => {
const r = cs.checkMaterials('test_recipe', { items: [{id:'iron_ore', quantity:3}] });
if (!r.canCraft) throw new Error('Expected canCraft=true');
});
test('fails with insufficient items', () => {
const r = cs.checkMaterials('test_recipe', { items: [{id:'iron_ore', quantity:1}] });
if (r.canCraft) throw new Error('Expected canCraft=false');
if (r.missing[0].need !== 2) throw new Error(`Expected need=2, got ${r.missing[0].need}`);
});
test('unknown recipe -> canCraft=false', () => {
const r = cs.checkMaterials('nope', { items: [] });
if (r.canCraft) throw new Error('Should be false');
});
test('_calcSkillLevel: 0 XP = level 1', () => expect(cs._calcSkillLevel(0)).toBe(1));
test('_calcSkillLevel: 200 XP = level 2', () => expect(cs._calcSkillLevel(200)).toBe(2));
test('_calcSkillLevel: caps at 50', () => expect(cs._calcSkillLevel(99999)).toBe(50));
test('_removeItems: reduces quantity', () => {
const inv = { items: [{id:'iron_ore', quantity:5}] };
cs._removeItems(inv, 'iron_ore', 3);
expect(inv.items[0].quantity).toBe(2);
});
test('_removeItems: removes item fully', () => {
const inv = { items: [{id:'iron_ore', quantity:2}] };
cs._removeItems(inv, 'iron_ore', 2);
expect(inv.items.length).toBe(0);
});
// ─── MarketSystem ────────────────────────────────────────────────────────────
console.log('\n💰 MarketSystem');
const { MarketSystem } = require('../systems/MarketSystem');
const ms = new MarketSystem();
test('has 5 tradeable resources', () => expect(Object.keys(ms.getTradeableResources()).length).toBe(5));
test('darkMatter minPrice = 10', () => expect(ms.getTradeableResources().darkMatter.minPrice).toBe(10));
test('metal maxPrice = 100', () => expect(ms.getTradeableResources().metal.maxPrice).toBe(100));
// ─── XP Progression ──────────────────────────────────────────────────────────
console.log('\n⭐ XP Progression (GDD §3.2: 500 × L^1.65)');
const xpReq = (L) => Math.round(500 * Math.pow(L, 1.65));
test('Level 1 = 500 XP', () => expect(xpReq(1)).toBe(500));
test('Level 5 > Level 2', () => expect(xpReq(5)).toBeGreaterThan(xpReq(2)));
test('Level 10 > 10,000', () => expect(xpReq(10)).toBeGreaterThan(10000));
test('Level 50 > 300,000', () => expect(xpReq(50)).toBeGreaterThan(300000));
// ─── Alliance Research Tree ───────────────────────────────────────────────────
console.log('\n🛡 Alliance Research Tree');
const tree = require('../data/gso/alliance/research_tree.json');
test('3 tiers', () => expect(tree.tiers.length).toBe(3));
test('Tier 1 has 3 techs', () => expect(tree.tiers[0].techs.length).toBe(3));
test('Tier 2 has prerequisites', () => {
const ok = tree.tiers[1].techs.some(t => t.prereq?.length > 0);
if (!ok) throw new Error('No tier 2 tech has prerequisites');
});
test('All techs have id/name/cost/effect', () => {
for (const tier of tree.tiers)
for (const t of tier.techs)
if (!t.id||!t.name||!t.cost||!t.effect)
throw new Error(`Tech missing fields: ${JSON.stringify(t)}`);
});
// ─── Locales ──────────────────────────────────────────────────────────────────
console.log('\n🌍 Locales');
const fs2 = require('fs'), path2 = require('path');
for (const lang of ['en','de','fr','es','ja','zh','ko','pt']) {
test(`${lang}.json has nav/resources/actions`, () => {
const locPath = path2.join(__dirname,'../../Client/locales',`${lang}.json`);
const loc = JSON.parse(fs2.readFileSync(locPath,'utf8'));
if (!loc.nav?.dashboard) throw new Error('Missing nav.dashboard');
if (!loc.resources?.metal) throw new Error('Missing resources.metal');
if (!loc.actions?.build) throw new Error('Missing actions.build');
});
}
// ─── Recipe time keys ─────────────────────────────────────────────────────────
console.log('\n⏱ Recipe Time Coverage');
const TIME_KEYS = ['craft_time_seconds','alloy_time_seconds','smelt_time_seconds','process_time_seconds','cook_time_seconds','harvest_time_seconds'];
test('alloy recipes have alloy_time_seconds', () => {
const dir = path2.join(__dirname,'../data/gso/recipes/alloys');
for (const f of fs2.readdirSync(dir).filter(f=>f.endsWith('.json'))) {
const rec = JSON.parse(fs2.readFileSync(path2.join(dir,f)));
if (!TIME_KEYS.some(k=>rec.recipe?.[k]!==undefined)) throw new Error(`${f} missing time key`);
}
});
test('smelting recipes have smelt_time_seconds', () => {
const dir = path2.join(__dirname,'../data/gso/recipes/smelting');
for (const f of fs2.readdirSync(dir).filter(f=>f.endsWith('.json'))) {
const rec = JSON.parse(fs2.readFileSync(path2.join(dir,f)));
if (!rec.recipe?.smelt_time_seconds) throw new Error(`${f} missing smelt_time_seconds`);
}
});
// ─── Summary ──────────────────────────────────────────────────────────────────
console.log(`\n${'─'.repeat(50)}`);
console.log(`Results: ${passed} passed, ${failed} failed`);
if (failed > 0) { console.log('❌ Some tests failed'); process.exit(1); }
else console.log('✅ All tests passed');