// 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'); }); });