const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { db, inject, getToken, cleanup } = require('./setup'); after(() => cleanup()); describe('Shop', () => { let studentToken, studentId, adminToken, itemId; before(async () => { const s = await getToken('student'); studentToken = s.token; studentId = s.userId; const a = await getToken('admin'); adminToken = a.token; // Give student some coins db.prepare('UPDATE users SET coins = 500 WHERE id = ?').run(studentId); // Create a shop item via admin const res = await inject('POST', '/api/shop/admin/items', { name: 'Test Frame', type: 'frame', price: 100, category: 'cosmetic', data: '{"css":"box-shadow:0 0 5px red"}', icon: 'star' }, adminToken); assert.equal(res.status, 200); itemId = res.body.id; }); it('lists items for student', async () => { const res = await inject('GET', '/api/shop/items', null, studentToken); assert.equal(res.status, 200); assert.ok(Array.isArray(res.body.items)); assert.equal(res.body.coins, 500); }); it('purchases item atomically', async () => { const res = await inject('POST', `/api/shop/items/${itemId}/purchase`, {}, studentToken); assert.equal(res.status, 200); assert.equal(res.body.ok, true); assert.equal(res.body.coins, 400); // 500 - 100 }); it('rejects duplicate purchase', async () => { const res = await inject('POST', `/api/shop/items/${itemId}/purchase`, {}, studentToken); assert.equal(res.status, 400); assert.ok(res.body.error.includes('уже купили')); }); it('rejects purchase with insufficient coins', async () => { // Create expensive item const cr = await inject('POST', '/api/shop/admin/items', { name: 'Expensive', type: 'title', price: 99999 }, adminToken); const expId = cr.body.id; const res = await inject('POST', `/api/shop/items/${expId}/purchase`, {}, studentToken); assert.equal(res.status, 400); assert.ok(res.body.error.includes('монет')); }); it('activates purchased item', async () => { const res = await inject('POST', '/api/shop/activate', { itemId }, studentToken); assert.equal(res.status, 200); assert.equal(res.body.type, 'frame'); }); it('validates activate type field', async () => { const res = await inject('POST', '/api/shop/activate', { type: 'invalid' }, studentToken); assert.equal(res.status, 400); }); it('validates admin create item fields', async () => { const res = await inject('POST', '/api/shop/admin/items', { name: 'X' }, adminToken); assert.equal(res.status, 400); // missing type, price }); it('validates admin award-coins fields', async () => { const res = await inject('POST', '/api/shop/admin/award-coins', { userId: 'abc' }, adminToken); assert.equal(res.status, 400); }); it('returns coin balance', async () => { const res = await inject('GET', '/api/shop/coins', null, studentToken); assert.equal(res.status, 200); assert.equal(typeof res.body.coins, 'number'); }); });