Files
Emberfall/tests/integration/hunt.test.js
bepis f1ae7b3119 feat: complete Phase 4 combat/hunting system
- Add content migration (003_content.sql): zones, monsters, items, loot tables
- Add inventory migration (004_inventory.sql) and logs migration (006_logs.sql)
- Add Mulberry32 PRNG (src/utils/rng.js) for deterministic combat
- Add combat system (src/game/combat.js) with turn order, crit, mage bonus, 20-round cap
- Add rewards/loot system (src/game/rewards.js) and leveling (src/game/leveling.js)
- Add hunt endpoint (POST /api/hunt) with full combat flow in DB transaction
- Add hunt integration tests and unit tests for rewards/leveling/combat
- Fix session middleware to use memory store in tests (avoids cleanup hang)
- Fix huntService transaction (db.transaction() returns wrapper, must call separately)
- Add huntService to helpers.js cache invalidation for test isolation
2026-10-20 17:46:18 +11:00

101 lines
3.2 KiB
JavaScript

// Integration tests for POST /api/hunt.
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { setup, teardown, cleanup } = require('../helpers');
/**
* Helper: register a user and return the session cookie string.
*/
async function registerUser(baseUrl, username, password) {
const res = await fetch(`${baseUrl}/api/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password: password || 'password123' }),
});
assert.strictEqual(res.status, 201);
const setCookie = res.headers.get('set-cookie');
assert.ok(setCookie, 'No Set-Cookie header');
return setCookie.split(';')[0]; // e.g. "sid=abc123"
}
/**
* Helper: create a character and return the response data.
*/
async function createCharacter(baseUrl, cookie, name) {
const res = await fetch(`${baseUrl}/api/character`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ name, class: 'warrior' }),
});
assert.strictEqual(res.status, 201);
return res.json();
}
describe('POST /api/hunt', () => {
let baseUrl;
before(async () => {
baseUrl = await setup();
});
after(async () => {
await teardown();
cleanup();
});
it('requires authentication', async () => {
const res = await fetch(`${baseUrl}/api/hunt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ zoneId: 1 }),
});
assert.strictEqual(res.status, 401);
});
it('hunts in zone 1 successfully', async () => {
const cookie = await registerUser(baseUrl, 'huntest1');
await createCharacter(baseUrl, cookie, 'HuntTest');
const res = await fetch(`${baseUrl}/api/hunt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ zoneId: 1 }),
});
assert.strictEqual(res.status, 200);
const data = await res.json();
assert.ok(data.ok);
assert.ok(data.data.winner === 'hero' || data.data.winner === 'monster');
assert.ok(Array.isArray(data.data.rounds));
assert.ok(data.data.rounds.length > 0);
assert.ok('xpGained' in data.data);
assert.ok('goldGained' in data.data);
assert.ok('hpRemaining' in data.data);
assert.ok('zone' in data.data);
assert.ok('monster' in data.data);
});
it('rejects invalid zone ID', async () => {
const cookie = await registerUser(baseUrl, 'huntest2');
await createCharacter(baseUrl, cookie, 'HuntTest2');
const res = await fetch(`${baseUrl}/api/hunt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ zoneId: 999 }),
});
assert.strictEqual(res.status, 404);
});
it('rejects missing zone ID', async () => {
const cookie = await registerUser(baseUrl, 'huntest3');
await createCharacter(baseUrl, cookie, 'HuntTest3');
const res = await fetch(`${baseUrl}/api/hunt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({}),
});
assert.strictEqual(res.status, 400);
});
});