Files
Emberfall/tests/integration/step-10.10.test.js
T
2026-10-23 01:38:39 +11:00

227 lines
9.5 KiB
JavaScript

// Step 10.10 — Rest endpoint: POST /api/character/rest
// Costs level*5 gold, heals to full max HP.
// Errors: NOTHING_TO_HEAL (422) if HP already full, INSUFFICIENT_GOLD (422).
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { setup, teardown, cleanup } = require('../helpers');
describe('Step 10.10 — Rest endpoint', () => {
let baseUrl;
let loginCookie;
function registerAndLogin(username) {
return fetch(`${baseUrl}/api/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password: 'password123' }),
}).then(() => fetch(`${baseUrl}/api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password: 'password123' }),
}));
}
function createHero(cookie, name) {
return fetch(`${baseUrl}/api/character`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ name, class: 'warrior' }),
});
}
function getCharacter(cookie) {
return fetch(`${baseUrl}/api/character`, {
method: 'GET',
headers: { Cookie: cookie },
});
}
function rest(cookie) {
return fetch(`${baseUrl}/api/character/rest`, {
method: 'POST',
headers: { Cookie: cookie },
});
}
function setupHero(username, name, hp, gold) {
return registerAndLogin(username)
.then((res) => {
loginCookie = res.headers.get('set-cookie');
return createHero(loginCookie, name);
})
.then(() => {
const db = require('../../src/db').getDb();
const userId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get(username).id);
const char = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userId);
db.prepare('UPDATE characters SET hp_current = ?, gold = ? WHERE id = ?').run(hp, gold, char.id);
});
}
before(async () => {
baseUrl = await setup();
});
after(async () => {
const { resetNow } = require('../../src/utils/time');
resetNow();
await teardown();
cleanup();
});
it('rest heals to full max HP and deducts level*5 gold', async () => {
await setupHero('rest1', 'RestHero1', 10, 100);
const charRes = await getCharacter(loginCookie);
assert.equal(charRes.status, 200);
const charData = await charRes.json();
const level = charData.data.character.level;
const maxHp = charData.data.derived.maxHp;
const goldBefore = charData.data.character.gold;
const cost = level * 5;
const restRes = await rest(loginCookie);
assert.equal(restRes.status, 200);
const restData = await restRes.json();
assert.equal(restData.ok, true);
// HP should be at max
assert.equal(restData.data.character.hp_current, maxHp);
// Gold should be reduced by cost
assert.equal(restData.data.character.gold, goldBefore - cost);
// Character data and derived included
assert.ok(restData.data.character, 'response includes updated character');
assert.ok(restData.data.derived, 'response includes updated derived stats');
// Verify in DB
const db = require('../../src/db').getDb();
const userId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('rest1').id);
const char = db.prepare('SELECT hp_current, gold FROM characters WHERE user_id = ?').get(userId);
assert.equal(char.hp_current, maxHp);
assert.equal(char.gold, goldBefore - cost);
});
it('rest at full HP returns 422 NOTHING_TO_HEAL and gold unchanged', async () => {
// Create a hero at level 5 (maxHp = 50 + 8*10 + 5*5 = 165), set hp to max
await setupHero('rest2', 'RestHero2', 10, 100);
// Boost to level 5 so maxHp is higher, then set hp to match maxHp
const db = require('../../src/db').getDb();
const userId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('rest2').id);
const char = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userId);
db.prepare('UPDATE characters SET level = 5, hp_current = 165 WHERE id = ?').run(char.id);
const charRes = await getCharacter(loginCookie);
assert.equal(charRes.status, 200);
const charData = await charRes.json();
const goldBefore = charData.data.character.gold;
const restRes = await rest(loginCookie);
assert.equal(restRes.status, 422);
const restData = await restRes.json();
assert.equal(restData.ok, false);
assert.equal(restData.error.code, 'NOTHING_TO_HEAL');
// Gold should be unchanged
assert.equal(goldBefore, charData.data.character.gold);
// Verify in DB
const db2 = require('../../src/db').getDb();
const charAfter = db2.prepare('SELECT hp_current, gold FROM characters WHERE id = ?').get(char.id);
assert.equal(charAfter.hp_current, 165);
assert.equal(charAfter.gold, 100);
});
it('rest with insufficient gold returns 422 INSUFFICIENT_GOLD and HP unchanged', async () => {
// Level 5 hero costs 25 gold to rest; give only 5 gold
await setupHero('rest3', 'RestHero3', 10, 5);
const db = require('../../src/db').getDb();
const userId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('rest3').id);
const char = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userId);
db.prepare('UPDATE characters SET level = 5, hp_current = 10, gold = 5 WHERE id = ?').run(char.id);
const charRes = await getCharacter(loginCookie);
assert.equal(charRes.status, 200);
const charData = await charRes.json();
const hpBefore = charData.data.character.hp_current;
const restRes = await rest(loginCookie);
assert.equal(restRes.status, 422);
const restData = await restRes.json();
assert.equal(restData.ok, false);
assert.equal(restData.error.code, 'INSUFFICIENT_GOLD');
// HP should be unchanged
assert.equal(hpBefore, charData.data.character.hp_current);
// Verify in DB
const db2 = require('../../src/db').getDb();
const charAfter = db2.prepare('SELECT hp_current, gold FROM characters WHERE id = ?').get(char.id);
assert.equal(charAfter.hp_current, 10);
assert.equal(charAfter.gold, 5);
});
it('rest on another user\'s character is rejected (cross-user isolation)', async () => {
// Create a second user (the "victim")
const victimRes = await registerAndLogin('rest4victim');
const victimCookie = victimRes.headers.get('set-cookie');
await createHero(victimCookie, 'VictimHero');
const db = require('../../src/db').getDb();
const victimUserId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('rest4victim').id);
const victimChar = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(victimUserId);
db.prepare('UPDATE characters SET hp_current = 10, gold = 100 WHERE id = ?').run(victimChar.id);
// Also set up rest1 with enough gold to succeed if it worked on their own char
const r1 = await registerAndLogin('rest1');
loginCookie = r1.headers.get('set-cookie');
await createHero(loginCookie, 'RestHero1');
const r1UserId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('rest1').id);
const r1Char = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(r1UserId);
db.prepare('UPDATE characters SET hp_current = 10, gold = 100 WHERE id = ?').run(r1Char.id);
// Try to rest with rest1's session — should succeed for rest1, NOT affect victim
const restRes = await rest(loginCookie);
assert.equal(restRes.status, 200);
// Victim's HP should be unchanged (rest1 can't rest on victim)
const db2 = require('../../src/db').getDb();
const charAfter = db2.prepare('SELECT hp_current FROM characters WHERE id = ?').get(victimChar.id);
assert.equal(charAfter.hp_current, 10);
});
it('rest cost scales with level: level 1 costs 5g, level 5 costs 25g', async () => {
// Level 1 hero — verify cost = 1 * 5 = 5
await setupHero('rest5a', 'RestHero5A', 10, 50);
const charResA = await getCharacter(loginCookie);
assert.equal(charResA.status, 200);
const charDataA = await charResA.json();
assert.ok(charDataA.data, 'response must have data');
assert.ok(charDataA.data.character, 'response data must have character');
const goldBeforeA = charDataA.data.character.gold;
const restResA = await rest(loginCookie);
assert.equal(restResA.status, 200);
const restDataA = await restResA.json();
assert.equal(restDataA.data.character.gold, goldBeforeA - 5);
// Level 5 hero — verify cost = 5 * 5 = 25
const resB = await registerAndLogin('rest5b');
loginCookie = resB.headers.get('set-cookie');
await createHero(loginCookie, 'RestHero5B');
const db = require('../../src/db').getDb();
const userIdB = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('rest5b').id);
const charB = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userIdB);
db.prepare('UPDATE characters SET level = 5, hp_current = 10, gold = 50 WHERE id = ?').run(charB.id);
const charResB = await getCharacter(loginCookie);
assert.equal(charResB.status, 200);
const charDataB = await charResB.json();
assert.ok(charDataB.data, 'response must have data');
assert.ok(charDataB.data.character, 'response data must have character');
const goldBeforeB = charDataB.data.character.gold;
const restResB = await rest(loginCookie);
assert.equal(restResB.status, 200);
const restDataB = await restResB.json();
assert.equal(restDataB.data.character.gold, goldBeforeB - 25);
});
});