// Integration tests for POST /api/character/allocate endpoint. const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { setup, teardown, cleanup } = require('../helpers'); describe('POST /api/character/allocate (integration)', () => { let baseUrl; let loginCookie; before(async () => { baseUrl = await setup(); }); after(async () => { const { resetNow } = require('../../src/utils/time'); resetNow(); await teardown(); cleanup(); }); function allocate(body) { return fetch(`${baseUrl}/api/character/allocate`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: loginCookie }, body: JSON.stringify(body), }); } function registerAndLogin(username) { return fetch(`${baseUrl}/api/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password: 'password123' }), }).then(() => fetch(`${baseUrl}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password: 'password123' }), })); } function createHero(cookie, name) { return fetch(`${baseUrl}/api/character`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ name, class: 'warrior' }), }); } it('spend 2 of 3 points on STR → str +2, unspent_points 1, derived attack increased', async () => { const res = await registerAndLogin('alloc1'); loginCookie = res.headers.get('set-cookie'); await createHero(loginCookie, 'Allocher1'); // Use the service directly to set up test state (bypasses HTTP/session layer) const db = require('../../src/db').getDb(); const svc = require('../../src/services/characterService'); const userId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('alloc1').id); const char = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userId); db.prepare('UPDATE characters SET unspent_points = 3 WHERE id = ?').run(char.id); const allocateRes = await allocate({ str: 2 }); assert.equal(allocateRes.status, 200); const body = await allocateRes.json(); assert.equal(body.ok, true); assert.equal(body.data.character.str, 10); // warrior base 8 + 2 assert.equal(body.data.character.unspent_points, 1); // Warrior attack = 5 + str*2 + level = 5 + 10*2 + 1 = 26 assert.equal(body.data.derived.attack, 26); }); it('spending 4 of 3 → 422 NOT_ENOUGH_POINTS, nothing changed', async () => { const res = await registerAndLogin('alloc2'); loginCookie = res.headers.get('set-cookie'); await createHero(loginCookie, 'Allocher2'); const db = require('../../src/db').getDb(); const userId = Number(db.prepare('SELECT id FROM users WHERE username = ?').get('alloc2').id); const char = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userId); db.prepare('UPDATE characters SET unspent_points = 3 WHERE id = ?').run(char.id); const allocateRes = await allocate({ str: 4 }); assert.equal(allocateRes.status, 422); const body = await allocateRes.json(); assert.equal(body.ok, false); assert.equal(body.error.code, 'NOT_ENOUGH_POINTS'); // Verify nothing changed in DB const refreshed = db.prepare('SELECT str, unspent_points FROM characters WHERE id = ?').get(char.id); assert.equal(refreshed.str, 8); // warrior base unchanged assert.equal(refreshed.unspent_points, 3); // points unchanged }); it('negative values → 400', async () => { const res = await registerAndLogin('alloc3'); loginCookie = res.headers.get('set-cookie'); await createHero(loginCookie, 'Allocher3'); const allocateRes = await allocate({ str: -1 }); assert.equal(allocateRes.status, 400); const body = await allocateRes.json(); assert.equal(body.ok, false); }); it('float values → 400', async () => { const res = await registerAndLogin('alloc4'); loginCookie = res.headers.get('set-cookie'); await createHero(loginCookie, 'Allocher4'); const allocateRes = await allocate({ str: 1.5 }); assert.equal(allocateRes.status, 400); const body = await allocateRes.json(); assert.equal(body.ok, false); }); it('unknown keys → 400', async () => { const res = await registerAndLogin('alloc5'); loginCookie = res.headers.get('set-cookie'); await createHero(loginCookie, 'Allocher5'); const allocateRes = await allocate({ str: 1, luck: 1 }); assert.equal(allocateRes.status, 400); const body = await allocateRes.json(); assert.equal(body.ok, false); }); it('another user cannot affect user A\'s character', async () => { const db = require('../../src/db').getDb(); // Get user A's character (from first test) const charA = db.prepare('SELECT id, str FROM characters WHERE name = ?').get('Allocher1'); const oldStr = charA.str; // User B creates hero and tries to allocate const res = await registerAndLogin('alloc_b'); loginCookie = res.headers.get('set-cookie'); await createHero(loginCookie, 'AllocherB'); // User B allocates on their own character (which has 0 unspent points) const allocateRes = await allocate({ str: 1 }); assert.equal(allocateRes.status, 422); // NOT_ENOUGH_POINTS for user B // User A's character is untouched const refreshed = db.prepare('SELECT str FROM characters WHERE id = ?').get(charA.id); assert.equal(refreshed.str, oldStr); }); });