// Integration tests for GET /api/zones and GET /api/combat-log. const { describe, it, before, after } = require('node:test'); const assert = require('node:assert/strict'); const { setup, teardown, cleanup } = require('../helpers'); /** * Register a user and log them in, returning the session cookie string. * Node.js fetch doesn't persist cookies across calls, so we extract and reuse them. */ async function registerAndLogin(baseUrl, username, password) { // Register await fetch(`${baseUrl}/api/auth/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); // Login — extract the session cookie from Set-Cookie const res = await fetch(`${baseUrl}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); assert.equal(res.status, 200); const json = await res.json(); assert.equal(json.ok, true); // Extract session cookie (express-session sets a cookie like "connect.sid=...; Path=/") const setCookie = res.headers.get('set-cookie'); assert.ok(setCookie, 'Login should set a session cookie'); const cookie = setCookie.split(';')[0]; // "connect.sid=..." return cookie; } function authHeaders(cookie) { return { Cookie: cookie }; } async function createCharacter(baseUrl, cookie, name = 'TestHero', cls = 'warrior') { const res = await fetch(`${baseUrl}/api/character`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ name, class: cls }), }); assert.equal(res.status, 201); const json = await res.json(); assert.equal(json.ok, true); return json; } describe('zones endpoint', () => { let baseUrl; before(async () => { baseUrl = await setup(); }); after(async () => { await teardown(); cleanup(); }); it('returns all zones with unlocked flag for a level-1 character', async () => { const cookie = await registerAndLogin(baseUrl, 'zoneuser1', 'password123'); await createCharacter(baseUrl, cookie, 'ZoneHero1'); const res = await fetch(`${baseUrl}/api/zones`, { headers: authHeaders(cookie) }); assert.equal(res.status, 200); const json = await res.json(); assert.equal(json.ok, true); const zones = json.data; assert.ok(Array.isArray(zones)); assert.ok(zones.length >= 3); // First zone (Whispering Meadows, min_level=1) should be unlocked for level-1 hero const meadows = zones.find((z) => z.slug === 'whispering-meadows'); assert.ok(meadows); assert.equal(meadows.unlocked, true); assert.equal(meadows.energy_cost, 5); assert.equal(meadows.min_level, 1); // Gloomroot Forest (min_level=5) should be locked const forest = zones.find((z) => z.slug === 'gloomroot-forest'); assert.ok(forest); assert.equal(forest.unlocked, false); assert.equal(forest.min_level, 5); // Ashfang Crags (min_level=10) should be locked const crags = zones.find((z) => z.slug === 'ashfang-crag'); assert.ok(crags); assert.equal(crags.unlocked, false); assert.equal(crags.min_level, 10); }); it('returns unlocked=true when character level meets zone requirement', async () => { const cookie = await registerAndLogin(baseUrl, 'zoneuser2', 'password123'); await createCharacter(baseUrl, cookie, 'ZoneHero2'); // Level up the character to 5 const db = require('../../src/db').getDb(); db.prepare( 'UPDATE characters SET level = 5, xp = 0, unspent_points = 0 WHERE user_id = (SELECT id FROM users WHERE username = ?)' ).run('zoneuser2'); const res = await fetch(`${baseUrl}/api/zones`, { headers: authHeaders(cookie) }); assert.equal(res.status, 200); const json = await res.json(); const forest = json.data.find((z) => z.slug === 'gloomroot-forest'); assert.equal(forest.unlocked, true); const crags = json.data.find((z) => z.slug === 'ashfang-crag'); assert.equal(crags.unlocked, false); }); it('rejects without authentication', async () => { const res = await fetch(`${baseUrl}/api/zones`); assert.equal(res.status, 401); const json = await res.json(); assert.equal(json.ok, false); assert.equal(json.error.code, 'UNAUTHENTICATED'); }); }); describe('combat-log endpoint', () => { let baseUrl; before(async () => { baseUrl = await setup(); }); after(async () => { await teardown(); cleanup(); }); it('returns empty list when no combat has occurred', async () => { const cookie = await registerAndLogin(baseUrl, 'loguser', 'password123'); await createCharacter(baseUrl, cookie, 'LogHero', 'mage'); const res = await fetch(`${baseUrl}/api/combat-log?limit=10`, { headers: authHeaders(cookie) }); assert.equal(res.status, 200); const json = await res.json(); assert.equal(json.ok, true); assert.equal(json.data.length, 0); }); it('rejects without authentication', async () => { const res = await fetch(`${baseUrl}/api/combat-log`); assert.equal(res.status, 401); const json = await res.json(); assert.equal(json.ok, false); }); });