// Integration tests for step 10.14: Material sell prices use item-level formula. const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { setup, teardown, cleanup } = require('../helpers'); describe('step-10.14 material sell prices (item-level)', () => { 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 grantItem(characterId, itemId, quantity = 1) { const db = require('../../src/db').getDb(); const NOW = 1_700_000_000_000; const existing = db.prepare( 'SELECT id, quantity FROM inventory WHERE character_id = ? AND item_id = ?' ).get(characterId, itemId); if (existing) { db.prepare('UPDATE inventory SET quantity = quantity + ? WHERE id = ?').run(quantity, existing.id); } else { db.prepare( 'INSERT INTO inventory (character_id, item_id, quantity, equipped_slot, acquired_at) VALUES (?, ?, ?, NULL, ?)' ).run(characterId, itemId, quantity, NOW); } } // ─── Rat Tail (common, min_level 1) — always 2g regardless of hero level ── it('Rat Tail sells for 2g at hero level 1', async () => { const cookie = await registerAndLogin(`s14a${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14A', 'warrior'); grantItem(characterId, 17, 1); // Rat Tail const res = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 17, quantity: 1 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); // Character starts with 50g (DB default), sells Rat Tail → 52g assert.equal(body.data.character.gold, 52); }); it('Rat Tail sells for 2g at hero level 10', async () => { const cookie = await registerAndLogin(`s14b${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14B', 'warrior'); grantItem(characterId, 17, 1); // Rat Tail const res = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 17, quantity: 1 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); assert.equal(body.data.character.gold, 52); }); it('Rat Tail sells for 2g at hero level 50', async () => { const cookie = await registerAndLogin(`s14c${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14C', 'warrior'); grantItem(characterId, 17, 1); // Rat Tail const res = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 17, quantity: 1 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); assert.equal(body.data.character.gold, 52); }); // ─── Drake Scale (rare, min_level 14) — always 112g regardless of hero level ── it('Drake Scale sells for 112g at hero level 14', async () => { const cookie = await registerAndLogin(`s14d${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14D', 'warrior'); grantItem(characterId, 21, 1); // Drake Scale const res = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 21, quantity: 1 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); // Character starts with 50g (DB default), sells Drake Scale → 162g assert.equal(body.data.character.gold, 162); }); it('Drake Scale sells for 112g at hero level 50', async () => { const cookie = await registerAndLogin(`s14e${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14E', 'warrior'); grantItem(characterId, 21, 1); // Drake Scale const res = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 21, quantity: 1 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); assert.equal(body.data.character.gold, 162); }); // ─── Shop-listed price equals actual payment ────────────────────────── it('shop-listed sellPrice equals the amount actually paid on sell', async () => { const cookie = await registerAndLogin(`s14f${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14F', 'warrior'); grantItem(characterId, 17, 3); // 3 Rat Tails // Get shop stock to see the listed sellPrice. const stockRes = await fetch(`${baseUrl}/api/shop`, { headers: { Cookie: cookie }, }); const stockBody = await stockRes.json(); assert.equal(stockRes.status, 200); const ratTail = stockBody.data.stock.find((i) => i.itemId === 17); assert.ok(ratTail, 'Rat Tail should be in stock'); const listedPrice = ratTail.sellPrice; assert.equal(listedPrice, 2, 'listed sellPrice for Rat Tail should be 2'); // Sell 1 Rat Tail. const sellRes = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 17, quantity: 1 }), }); const sellBody = await sellRes.json(); assert.equal(sellRes.status, 200); assert.equal(sellBody.ok, true); // Gold should increase by exactly the listed price (50 + 2 = 52). assert.equal(sellBody.data.character.gold, 50 + listedPrice); }); // ─── Multi-quantity sell ────────────────────────────────────────────── it('selling 5 Rat Tails yields exactly 10g (5 × 2g)', async () => { const cookie = await registerAndLogin(`s14g${userSeq++}`, 'pass1234'); const characterId = await createHero(cookie, 'S14G', 'warrior'); grantItem(characterId, 17, 5); const res = await fetch(`${baseUrl}/api/shop/sell`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ itemId: 17, quantity: 5 }), }); const body = await res.json(); assert.equal(res.status, 200); assert.equal(body.ok, true); // 50 + 5*2 = 60 assert.equal(body.data.character.gold, 60); }); });