step 10.14: done
This commit is contained in:
@@ -320,8 +320,8 @@ describe('shop', () => {
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(body.ok, true);
|
||||
// Uncommon material sell: floor(4 * level / min_level) = floor(4 * 5 / 5) = 4
|
||||
assert.equal(body.data.character.gold, 104); // 100 + 4
|
||||
// Uncommon material sell: 4 * min_level = 4 * 5 = 20
|
||||
assert.equal(body.data.character.gold, 120); // 100 + 20
|
||||
});
|
||||
|
||||
it('sells uncommon material using rarity default', async () => {
|
||||
@@ -339,8 +339,8 @@ describe('shop', () => {
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(body.ok, true);
|
||||
// Uncommon material sell: floor(4 * 10 / 10) = 4
|
||||
assert.equal(body.data.character.gold, 104); // 100 + 4
|
||||
// Uncommon material sell: 4 * min_level = 4 * 10 = 40
|
||||
assert.equal(body.data.character.gold, 140); // 100 + 40
|
||||
});
|
||||
|
||||
it('rejects selling a non-material item', async () => {
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user