// Direct unit tests for allocatePoints service function. const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { setup, teardown, cleanup } = require('../helpers'); const NOW = 1_700_000_000_000; describe('allocatePoints service (direct)', () => { let db, svc, userIdA; before(async () => { await setup(); db = require('../../src/db').getDb(); svc = require('../../src/services/characterService'); // Create a user and character directly const uid = Number(db.prepare('INSERT INTO users (username, password_hash, created_at) VALUES (?, ?, ?)').run('allocsvc', 'x', NOW).lastInsertRowid); const { id } = svc.createCharacter(uid, 'SvcHero', 'warrior'); userIdA = uid; // Give the character 3 unspent points db.prepare('UPDATE characters SET unspent_points = 3 WHERE id = ?').run(id); }); after(async () => { const { resetNow } = require('../../src/utils/time'); resetNow(); await teardown(); cleanup(); }); it('spend 2 of 3 points on STR → str +2, unspent_points 1', () => { const charBefore = db.prepare('SELECT * FROM characters WHERE user_id = ?').get(userIdA); assert.equal(charBefore.unspent_points, 3); assert.equal(charBefore.str, 8); // warrior base const result = svc.allocatePoints(userIdA, { str: 2 }); assert.equal(result.character.str, 10); // 8 + 2 assert.equal(result.character.unspent_points, 1); // 3 - 2 // Warrior attack = 5 + str*2 + level = 5 + 10*2 + 1 = 26 assert.equal(result.derived.attack, 26); }); it('spending 4 of 3 → NOT_ENOUGH_POINTS, nothing changed', () => { const charBefore = db.prepare('SELECT str, unspent_points FROM characters WHERE user_id = ?').get(userIdA); const oldStr = charBefore.str; const oldPts = charBefore.unspent_points; try { svc.allocatePoints(userIdA, { str: 4 }); assert.fail('Should have thrown'); } catch (err) { assert.equal(err.code, 'NOT_ENOUGH_POINTS'); } // Verify nothing changed const charAfter = db.prepare('SELECT str, unspent_points FROM characters WHERE user_id = ?').get(userIdA); assert.equal(charAfter.str, oldStr); assert.equal(charAfter.unspent_points, oldPts); }); it('spend all 3 evenly (1 each) → unspent_points 0, all stats increased', () => { // First give points back db.prepare('UPDATE characters SET unspent_points = 3, str = 8, agi = 5, int = 5, vit = 8 WHERE user_id = ?').run(userIdA); const charBefore = db.prepare('SELECT * FROM characters WHERE user_id = ?').get(userIdA); assert.equal(charBefore.unspent_points, 3); const result = svc.allocatePoints(userIdA, { str: 1, agi: 1, vit: 1 }); assert.equal(result.character.unspent_points, 0); assert.equal(result.character.str, 9); assert.equal(result.character.agi, 6); assert.equal(result.character.vit, 9); }); it('negative values throw INVALID_FIELD', () => { try { svc.allocatePoints(userIdA, { str: -1 }); assert.fail('Should have thrown'); } catch (err) { assert.equal(err.code, 'INVALID_FIELD'); } }); it('float values throw INVALID_FIELD', () => { try { svc.allocatePoints(userIdA, { str: 1.5 }); assert.fail('Should have thrown'); } catch (err) { assert.equal(err.code, 'INVALID_FIELD'); } }); it('unknown keys throw INVALID_FIELD', () => { try { svc.allocatePoints(userIdA, { str: 1, luck: 1 }); assert.fail('Should have thrown'); } catch (err) { assert.equal(err.code, 'INVALID_FIELD'); } }); });