// Integration tests for step 10.15: Non-stackable items get one inventory row per unit. const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { setup, teardown, cleanup } = require('../helpers'); describe('step-10.15 non-stackable items: one row per unit', () => { let baseUrl, userSeq = 0; before(async () => { baseUrl = await setup(); }); after(async () => { teardown(); cleanup(); }); // ─── helpers ────────────────────────────────────────────────────────── async function registerAndLogin(username, password) { await fetch(`${baseUrl}/api/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); const res = await fetch(`${baseUrl}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); assert.equal(res.status, 200); return res.headers.get('set-cookie') || ''; } async function createHero(cookie, name, cls) { const res = await fetch(`${baseUrl}/api/character`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ name, class: cls }), }); assert.equal(res.status, 201); const { data } = await res.json(); return data.character.id; } function countInvRows(characterId, itemId) { const db = require('../../src/db').getDb(); return db.prepare( 'SELECT COUNT(*) AS cnt FROM inventory WHERE character_id = ? AND item_id = ?' ).get(characterId, itemId).cnt; } function getInvRows(characterId, itemId) { const db = require('../../src/db').getDb(); return db.prepare( 'SELECT id, quantity FROM inventory WHERE character_id = ? AND item_id = ? ORDER BY id' ).all(characterId, itemId); } // ─── Non-stackable items (weapons) ──────────────────────────────────── it('buying 3 non-stackable weapons creates 3 inventory rows', async () => { const cookie = await registerAndLogin(`s15b${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S15B', 'warrior'); // Give hero enough gold for 3 Rusty Swords (60g). const db = require('../../src/db').getDb(); db.prepare('UPDATE characters SET gold = 100 WHERE id = ?').run(characterId); // Buy 3 Rusty Swords (item 1, non-stackable, 20g each = 60g total). const res = await fetch(`${baseUrl}/api/shop/buy`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 1, quantity: 3 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); assert.equal(body.data.character.gold, 40); // 100 - 60 // DB: should be exactly 4 rows (1 starter + 3 bought), each with quantity 1. const rows = getInvRows(characterId, 1); assert.equal(rows.length, 4, 'should have 4 inventory rows for 1 starter + 3 bought non-stackable items'); for (const row of rows) { assert.equal(row.quantity, 1, 'each row should have quantity 1'); } // API: each row should show quantity 1. const swordItems = body.data.items.filter((i) => i.itemId === 1); assert.equal(swordItems.length, 4, 'API should show 4 sword entries'); for (const item of swordItems) { assert.equal(item.quantity, 1); } }); it('buying more non-stackable items adds more rows (not merging)', async () => { const cookie = await registerAndLogin(`s15c${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S15C', 'warrior'); const db = require('../../src/db').getDb(); db.prepare('UPDATE characters SET gold = 100 WHERE id = ?').run(characterId); // Buy 2 Rusty Swords. const res1 = await fetch(`${baseUrl}/api/shop/buy`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 1, quantity: 2 }), }); const body1 = await res1.json(); assert.equal(body1.ok, true); // 1 starter + 2 bought = 3 rows. assert.equal(countInvRows(characterId, 1), 3); // Buy 2 more. const res2 = await fetch(`${baseUrl}/api/shop/buy`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 1, quantity: 2 }), }); const body2 = await res2.json(); assert.equal(body2.ok, true); // Should now have 5 rows total (1 starter + 2 + 2 bought), each qty 1. assert.equal(countInvRows(characterId, 1), 5, 'should have 5 rows after buying 2+2'); const rows = getInvRows(characterId, 1); for (const row of rows) { assert.equal(row.quantity, 1); } }); // ─── Stackable items still merge ────────────────────────────────────── it('buying stackable consumables merges into one row', async () => { const cookie = await registerAndLogin(`s15d${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S15D', 'warrior'); const db = require('../../src/db').getDb(); db.prepare('UPDATE characters SET gold = 100 WHERE id = ?').run(characterId); // Starter kit gives 2 Minor Healing Potions (item 14, stackable). // Buy 3 more → should merge to quantity 5 in ONE row. const res = await fetch(`${baseUrl}/api/shop/buy`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 14, quantity: 3 }), }); const body = await res.json(); assert.equal(body.ok, true); // Potion rows: should be exactly 1 row with quantity 5. const potionRows = getInvRows(characterId, 14); assert.equal(potionRows.length, 1, 'stackable items should merge into 1 row'); assert.equal(potionRows[0].quantity, 5, 'quantity should be 5 (2 starter + 3 bought)'); }); // ─── Non-stackable armor ────────────────────────────────────────────── it('buying 2 non-stackable armor pieces creates 2 rows', async () => { const cookie = await registerAndLogin(`s15e${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S15E', 'warrior'); const db = require('../../src/db').getDb(); db.prepare('UPDATE characters SET gold = 100 WHERE id = ?').run(characterId); // Leather Jerkin (item 8, armor, non-stackable, 25g each). const res = await fetch(`${baseUrl}/api/shop/buy`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 8, quantity: 2 }), }); const body = await res.json(); assert.equal(body.ok, true); assert.equal(body.data.character.gold, 50); // 100 - 2*25 const rows = getInvRows(characterId, 8); assert.equal(rows.length, 2, 'should have 2 rows for 2 non-stackable armor'); for (const row of rows) { assert.equal(row.quantity, 1); } }); });