Files
Emberfall/tests/integration/step-10.12.test.js
T
2026-10-23 04:33:19 +11:00

197 lines
7.1 KiB
JavaScript

// Integration tests for step 10.12: Equipment bonuses in combat, profile, and potions.
// ONE shared getEquipmentBonuses() function powers all of these.
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { setup, teardown, cleanup } = require('../helpers');
describe('step-10.12 equipment bonuses', () => {
let baseUrl, db;
const NOW = 1_700_000_000_000;
let userSeq = 0;
before(async () => {
baseUrl = await setup();
db = require('../../src/db').getDb();
});
after(async () => { teardown(); cleanup(); });
// --- helpers ---
/** Register a user and return the Set-Cookie header value. */
async function register(username, password) {
const res = await fetch(`${baseUrl}/api/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const setCookie = res.headers.get('set-cookie') || '';
assert.equal(res.status, 201);
return setCookie;
}
/** Login and return the Set-Cookie header value. */
async function login(username, password) {
const res = await fetch(`${baseUrl}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const setCookie = res.headers.get('set-cookie') || '';
assert.equal(res.status, 200);
return setCookie;
}
/** Create a hero and return { cookie, characterId }. Uses unique names per test. */
async function createHero(cookie, idx) {
const name = `E12${idx}`;
const res = await fetch(`${baseUrl}/api/character`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ name, class: 'warrior' }),
});
assert.equal(res.status, 201, `createHero failed for ${name}: ${res.status}`);
const { data } = await res.json();
return { characterId: data.character.id, cookie };
}
/** Grant an item to a character's inventory (bypasses hunting). */
function grantItem(characterId, itemId, quantity = 1) {
db.prepare(
'INSERT INTO inventory (character_id, item_id, quantity, equipped_slot, acquired_at) VALUES (?, ?, ?, NULL, ?)'
).run(characterId, itemId, quantity, NOW);
}
/** Equip an item by calling the HTTP endpoint. */
async function httpEquip(cookie, itemId, slot) {
const res = await fetch(`${baseUrl}/api/inventory/equip`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ itemId, slot }),
});
assert.equal(res.status, 200);
return res.json();
}
/** Unequip an item by calling the HTTP endpoint. */
async function httpUnequip(cookie, slot) {
const res = await fetch(`${baseUrl}/api/inventory/unequip`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ slot }),
});
assert.equal(res.status, 200);
return res.json();
}
/** GET /api/character and return { character, derived }. */
async function getCharacter(cookie) {
const res = await fetch(`${baseUrl}/api/character`, {
headers: { Cookie: cookie },
});
assert.equal(res.status, 200);
const { data } = await res.json();
return data;
}
// --- tests ---
it('GET /api/character derived stats include equipment bonuses', async () => {
const cookie = await register(`e1012a${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 1);
// Baseline: no equipment.
const before = await getCharacter(cookie);
const baseDefense = before.derived.defense;
const baseAttack = before.derived.attack;
// Equip Rusty Sword (item 1, +3 attack) and Leather Jerkin (item 8, +3 defense).
grantItem(characterId, 1);
grantItem(characterId, 8);
await httpEquip(cookie, 1, 'weapon');
await httpEquip(cookie, 8, 'armor');
const after = await getCharacter(cookie);
// Attack should include +3 from weapon.
assert.equal(after.derived.attack, baseAttack + 3,
`derived.attack should include +3 weapon bonus: was ${baseAttack}, now ${after.derived.attack}`);
// Defense should include +3 from armor.
assert.equal(after.derived.defense, baseDefense + 3,
`derived.defense should include +3 armor bonus: was ${baseDefense}, now ${after.derived.defense}`);
});
it('equipment bonus_vit raises max HP exactly once', async () => {
const cookie = await register(`e1012b${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 2);
// Baseline max HP (warrior, vit=8, level 1): 50 + 8*10 + 1*5 = 135.
const before = await getCharacter(cookie);
const baseMaxHp = before.derived.maxHp;
// Equip Wooden Charm (item 11, trinket, +1 vit).
grantItem(characterId, 11);
await httpEquip(cookie, 11, 'trinket');
const after = await getCharacter(cookie);
// +1 vit → +10 max HP.
assert.equal(after.derived.maxHp, baseMaxHp + 10,
`maxHp should increase by +10 for +1 vit: was ${baseMaxHp}, now ${after.derived.maxHp}`);
});
it('potion heal caps at equipment-inclusive maxHp', async () => {
const cookie = await register(`e1012c${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 3);
// Equip Wooden Charm (+1 vit) → maxHp = 135 + 10 = 145.
grantItem(characterId, 11);
await httpEquip(cookie, 11, 'trinket');
const before = await getCharacter(cookie);
const equipMaxHp = before.derived.maxHp;
// Set HP to 60.
db.prepare('UPDATE characters SET hp_current = 60 WHERE id = ?').run(characterId);
// Grant and use Minor Healing Potion (item 14, heal:50).
grantItem(characterId, 14, 3);
const res = await fetch(`${baseUrl}/api/inventory/use`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ itemId: 14 }),
});
assert.equal(res.status, 200);
// HP should be capped at equipment-inclusive maxHp.
const afterMaxHp = (await getCharacter(cookie)).derived.maxHp;
assert.equal(afterMaxHp, equipMaxHp, 'maxHp should be unchanged after potion');
const hp = db.prepare('SELECT hp_current FROM characters WHERE id = ?').get(characterId).hp_current;
assert.equal(hp, Math.min(60 + 50, equipMaxHp),
`potion heal should cap at equipment-inclusive maxHp (${equipMaxHp})`);
});
it('unequipping removes equipment bonuses from derived stats', async () => {
const cookie = await register(`e1012d${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 4);
// Equip Rusty Sword (+3 attack).
grantItem(characterId, 1);
await httpEquip(cookie, 1, 'weapon');
const withEquip = await getCharacter(cookie);
const equippedAttack = withEquip.derived.attack;
// Unequip.
await httpUnequip(cookie, 'weapon');
const withoutEquip = await getCharacter(cookie);
// Attack should drop back to the baseline.
assert.equal(withoutEquip.derived.attack, equippedAttack - 3,
`attack should drop by 3 after unequipping weapon: was ${equippedAttack}, now ${withoutEquip.derived.attack}`);
});
});