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
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user