step 10.13: done

This commit is contained in:
bepis
2026-10-23 05:16:35 +11:00
parent 4277b9c273
commit 32fb0eee9a
8 changed files with 290 additions and 78 deletions
+169
View File
@@ -0,0 +1,169 @@
// Integration tests for step 10.13: Starter kit on character creation.
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { setup, teardown, cleanup } = require('../helpers');
describe('step-10.13 starter kit', () => {
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 }. */
async function createHero(cookie, name, classKey) {
const res = await fetch(`${baseUrl}/api/character`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ name, class: classKey }),
});
assert.equal(res.status, 201, `createHero failed for ${name}: ${res.status}`);
const { data } = await res.json();
return { characterId: data.character.id, cookie };
}
/** 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;
}
// --- warrior tests ---
it('warrior starts with Rusty Sword equipped + 2 Minor Healing Potions', async () => {
const cookie = await register(`s13w${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 'S13Warrior', 'warrior');
// Inventory: exactly 2 rows (weapon + potion stack).
const rows = db.prepare(
'SELECT item_id, quantity, equipped_slot FROM inventory WHERE character_id = ? ORDER BY item_id'
).all(characterId);
assert.equal(rows.length, 2, `expected 2 inventory rows, got ${rows.length}`);
// Weapon is Rusty Sword (item 1), equipped in weapon slot.
const weapon = rows.find(r => r.equipped_slot === 'weapon');
assert.ok(weapon, 'expected an equipped weapon row');
assert.equal(weapon.item_id, 1, 'warrior weapon should be Rusty Sword (item 1)');
// Potions: one stack row, quantity 2.
const potions = rows.find(r => r.item_id === 14);
assert.ok(potions, 'expected a potion row');
assert.equal(potions.quantity, 2, 'should have 2 Minor Healing Potions');
// Derived attack includes the Rusty Sword bonus (+3 bonus_attack).
// Warrior: 5 + 8*2 + 1 + 3 = 25
const profile = await getCharacter(cookie);
assert.equal(profile.derived.attack, 25,
`warrior attack should be 25 (5 + 8*2 + 1 + 3): got ${profile.derived.attack}`);
});
// --- mage tests ---
it('mage starts with Apprentice Staff equipped + 2 Minor Healing Potions', async () => {
const cookie = await register(`s13m${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 'S13Mage', 'mage');
const rows = db.prepare(
'SELECT item_id, quantity, equipped_slot FROM inventory WHERE character_id = ? ORDER BY item_id'
).all(characterId);
assert.equal(rows.length, 2, `expected 2 inventory rows, got ${rows.length}`);
const weapon = rows.find(r => r.equipped_slot === 'weapon');
assert.ok(weapon, 'expected an equipped weapon row');
assert.equal(weapon.item_id, 2, 'mage weapon should be Apprentice Staff (item 2)');
const potions = rows.find(r => r.item_id === 14);
assert.ok(potions, 'expected a potion row');
assert.equal(potions.quantity, 2, 'should have 2 Minor Healing Potions');
// Derived attack includes the Apprentice Staff bonus (+1 int → primary goes 9→10).
// Mage: 5 + 10*2 + 1 + 0 = 26
const profile = await getCharacter(cookie);
assert.equal(profile.derived.attack, 26,
`mage attack should be 26 (5 + 10*2 + 1 + 0): got ${profile.derived.attack}`);
});
// --- ranger tests ---
it('ranger starts with Hunters Shortbow equipped + 2 Minor Healing Potions', async () => {
const cookie = await register(`s13r${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 'S13Ranger', 'ranger');
const rows = db.prepare(
'SELECT item_id, quantity, equipped_slot FROM inventory WHERE character_id = ? ORDER BY item_id'
).all(characterId);
assert.equal(rows.length, 2, `expected 2 inventory rows, got ${rows.length}`);
const weapon = rows.find(r => r.equipped_slot === 'weapon');
assert.ok(weapon, 'expected an equipped weapon row');
assert.equal(weapon.item_id, 3, 'ranger weapon should be Hunters Shortbow (item 3)');
const potions = rows.find(r => r.item_id === 14);
assert.ok(potions, 'expected a potion row');
assert.equal(potions.quantity, 2, 'should have 2 Minor Healing Potions');
// Derived attack includes the Shortbow bonus (+2 agi → primary goes 9→11).
// Ranger: 5 + 11*2 + 1 + 0 = 28
const profile = await getCharacter(cookie);
assert.equal(profile.derived.attack, 28,
`ranger attack should be 28 (5 + 11*2 + 1 + 0): got ${profile.derived.attack}`);
});
// --- failure path: duplicate name blocked ---
it('duplicate hero name is still rejected despite starter kit', async () => {
const cookie = await register(`s13x${Date.now()}`, 'pass1234');
const { characterId } = await createHero(cookie, 'S13X', 'warrior');
const res = await fetch(`${baseUrl}/api/character`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ name: 'S13X', class: 'mage' }),
});
assert.equal(res.status, 409);
const body = await res.json();
assert.equal(body.ok, false);
assert.equal(body.error.code, 'HERO_EXISTS');
// The first hero should have its starter kit intact (2 rows).
const rows = db.prepare(
'SELECT COUNT(*) as cnt FROM inventory WHERE character_id = ?'
).get(characterId);
assert.equal(rows.cnt, 2, 'starter kit should have 2 inventory rows');
});
});