step 6.3: done
This commit is contained in:
@@ -236,6 +236,39 @@ describe('shop', () => {
|
||||
assert.equal(body.error.code, 'VALIDATION_ERROR');
|
||||
});
|
||||
|
||||
it('concurrent double-buy: only one succeeds when gold covers exactly one', async () => {
|
||||
const { cookie, characterId } = await setupHero({ gold: 20 });
|
||||
|
||||
// Rusty Sword costs 20 gold — exactly enough for one.
|
||||
// Send two buy requests concurrently.
|
||||
const [resA, resB] = await Promise.all([
|
||||
fetch(`${baseUrl}/api/shop/buy`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders(cookie) },
|
||||
body: JSON.stringify({ itemId: 1, quantity: 1 }),
|
||||
}),
|
||||
fetch(`${baseUrl}/api/shop/buy`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders(cookie) },
|
||||
body: JSON.stringify({ itemId: 1, quantity: 1 }),
|
||||
}),
|
||||
]);
|
||||
|
||||
const bodyA = await resA.json();
|
||||
const bodyB = await resB.json();
|
||||
|
||||
// Exactly one should succeed, the other should fail with INSUFFICIENT_GOLD.
|
||||
const success = [bodyA, bodyB].filter((b) => b.ok === true);
|
||||
const failures = [bodyA, bodyB].filter((b) => b.ok === false);
|
||||
assert.equal(success.length, 1, 'exactly one buy should succeed');
|
||||
assert.equal(failures.length, 1, 'exactly one buy should fail');
|
||||
assert.equal(failures[0].error.code, 'INSUFFICIENT_GOLD');
|
||||
|
||||
// Gold should be 0 after one successful buy.
|
||||
const winner = success[0];
|
||||
assert.equal(winner.data.character.gold, 0);
|
||||
});
|
||||
|
||||
// ─── POST /api/shop/sell ──────────────────────────────────────────────
|
||||
|
||||
it('sells a material and increases gold', async () => {
|
||||
@@ -336,6 +369,31 @@ describe('shop', () => {
|
||||
assert.equal(body.error.code, 'NOT_FOUND');
|
||||
});
|
||||
|
||||
// ─── Sell equipped item rejection ─────────────────────────────────────
|
||||
|
||||
it('rejects selling an equipped item', async () => {
|
||||
const { cookie, characterId } = await setupHero({ gold: 100 });
|
||||
|
||||
// Grant and equip Rusty Sword (item 1, weapon)
|
||||
grantItem(characterId, 1, 1);
|
||||
|
||||
const db = require('../../src/db').getDb();
|
||||
db.prepare('UPDATE inventory SET equipped_slot = ? WHERE character_id = ? AND item_id = ?').run('weapon', characterId, 1);
|
||||
|
||||
// Try to sell the equipped Rusty Sword
|
||||
const res = await fetch(`${baseUrl}/api/shop/sell`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', ...authHeaders(cookie) },
|
||||
body: JSON.stringify({ itemId: 1, quantity: 1 }),
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
assert.equal(res.status, 400);
|
||||
assert.equal(body.ok, false);
|
||||
assert.equal(body.error.code, 'VALIDATION_ERROR');
|
||||
assert.ok(body.error.message.includes('Unequip'), 'should mention unequipping');
|
||||
});
|
||||
|
||||
// ─── Round-trip ───────────────────────────────────────────────────────
|
||||
|
||||
it('buy and sell round-trip: buy then sell loses gold (spread)', async () => {
|
||||
|
||||
Reference in New Issue
Block a user