// Integration smoke for server. Spin on random port, hit REST, check WS broadcast. 'use strict'; const http = require('http'); const { createServer } = require('./index'); let BASE; let handle; beforeEach(async () => { handle = createServer({ dbPath: ':memory:' }); await new Promise(r => handle.server.listen(0, r)); const addr = handle.server.address(); BASE = `http://127.0.0.1:${addr.port}`; }); afterEach(async () => { await new Promise(r => handle.close(r)); }); async function req(method, path, body) { const res = await fetch(`${BASE}${path}`, { method, headers: body ? { 'Content-Type': 'application/json' } : undefined, body: body ? JSON.stringify(body) : undefined, }); const text = await res.text(); const json = text ? JSON.parse(text) : null; return { status: res.status, json }; } describe('server REST', () => { test('health', async () => { const { status, json } = await req('GET', '/health'); expect(status).toBe(200); expect(json.ok).toBe(true); }); test('campaign create + list', async () => { const { json: c } = await req('POST', '/api/campaigns', { name: 'Test', backgroundUrl: '' }); expect(c.name).toBe('Test'); const { json: list } = await req('GET', '/api/campaigns'); expect(list).toHaveLength(1); expect(list[0].id).toBe(c.id); }); test('encounter create + add monster + start', async () => { const { json: c } = await req('POST', '/api/campaigns', { name: 'C' }); const { json: e } = await req('POST', `/api/campaigns/${c.id}/encounters`, { name: 'E' }); expect(e.participants).toEqual([]); const { json: addRes } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/participants`, { type: 'monster', name: 'Goblin', maxHp: 7, initMod: 2, isNpc: false, }); expect(addRes.encounter.participants).toHaveLength(1); expect(addRes.encounter.participants[0].name).toBe('Goblin'); expect(addRes.roll.total).toBeGreaterThan(2); const { json: started } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/start`); expect(started.isStarted).toBe(true); expect(started.round).toBe(1); expect(started.currentTurnParticipantId).toBe(addRes.encounter.participants[0].id); }); test('next turn advances', async () => { const { json: c } = await req('POST', '/api/campaigns', { name: 'C' }); const { json: e } = await req('POST', `/api/campaigns/${c.id}/encounters`, { name: 'E' }); const ids = []; for (let i = 0; i < 3; i++) { const { json: r } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/participants`, { type: 'monster', name: `M${i}`, maxHp: 10, initMod: i, }); ids.push(r.encounter.participants[r.encounter.participants.length - 1].id); } await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/start`); const { json: t1 } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/nextTurn`); expect(t1.currentTurnParticipantId).not.toBe(t1.turnOrderIds[0]); }); test('damage to 0 deactivates', async () => { const { json: c } = await req('POST', '/api/campaigns', { name: 'C' }); const { json: e } = await req('POST', `/api/campaigns/${c.id}/encounters`, { name: 'E' }); const { json: r } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/participants`, { type: 'monster', name: 'Orc', maxHp: 5, initMod: 0, }); const pid = r.encounter.participants[0].id; await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/start`); const { json: after } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/participants/${pid}/hp`, { changeType: 'damage', amount: 5, }); expect(after.participants[0].currentHp).toBe(0); expect(after.participants[0].isActive).toBe(false); }); test('error: start with no participants', async () => { const { json: c } = await req('POST', '/api/campaigns', { name: 'C' }); const { json: e } = await req('POST', `/api/campaigns/${c.id}/encounters`, { name: 'E' }); const { status, json } = await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/start`); expect(status).toBe(400); expect(json.error).toMatch(/participants/i); }); test('logs recorded on actions', async () => { const { json: c } = await req('POST', '/api/campaigns', { name: 'C' }); const { json: e } = await req('POST', `/api/campaigns/${c.id}/encounters`, { name: 'E' }); await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/participants`, { type: 'monster', name: 'Goblin', maxHp: 7, initMod: 2, }); await req('POST', `/api/campaigns/${c.id}/encounters/${e.id}/start`); const { json: logs } = await req('GET', '/api/logs'); expect(logs.length).toBeGreaterThanOrEqual(2); expect(logs.some(l => /Combat started/.test(l.message))).toBe(true); }); });