step 10.15: done

This commit is contained in:
bepis
2026-10-23 06:16:38 +11:00
parent 2a79c4ea43
commit 81794c7dbd
7 changed files with 240 additions and 34 deletions
+172
View File
@@ -0,0 +1,172 @@
// 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);
}
});
});