Files
Emberfall/tests/integration/hero-profile.test.js
2026-10-21 08:05:25 +11:00

159 lines
5.7 KiB
JavaScript

// Hero profile integration tests: GET /api/heroes/:name (public, no auth).
const { describe, it, before, after } = require('node:test');
const assert = require('node:assert/strict');
const { setup, teardown, cleanup } = require('../helpers');
describe('hero profile (public)', () => {
let baseUrl, userSeq = 0;
before(async () => {
baseUrl = await setup();
});
after(async () => {
await teardown();
cleanup();
});
/**
* Register, login, create a character, return cookie + characterId.
*/
async function registerAndLogin(username, password) {
await fetch(`${baseUrl}/api/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
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 setCookie = res.headers.get('set-cookie');
assert.ok(setCookie, 'Login should set a session cookie');
return setCookie.split(';')[0];
}
async function createHero(cookie, heroName, heroClass) {
const res = await fetch(`${baseUrl}/api/character`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Cookie: cookie },
body: JSON.stringify({ name: heroName, class: heroClass }),
});
assert.equal(res.status, 201);
const json = await res.json();
return json.data.character.id;
}
function updateHeroField(characterId, column, value) {
const db = require('../../src/db').getDb();
db.prepare(`UPDATE characters SET ${column} = ? WHERE id = ?`).run(value, characterId);
}
function equipItem(characterId, itemId, slot) {
const db = require('../../src/db').getDb();
db.prepare('UPDATE inventory SET equipped_slot = ? WHERE id = ?').run(slot, itemId);
}
it('returns public profile for an existing hero', async () => {
const cookie = await registerAndLogin(`herouser1`, 'password123');
const characterId = await createHero(cookie, 'DragonSlayer', 'warrior');
// Tweak some fields so they show up in the response.
updateHeroField(characterId, 'level', 5);
updateHeroField(characterId, 'kills', 42);
updateHeroField(characterId, 'gold', 300);
updateHeroField(characterId, 'deaths', 3);
const res = await fetch(`${baseUrl}/api/heroes/DragonSlayer`);
assert.equal(res.status, 200);
const json = await res.json();
assert.equal(json.ok, true);
const data = json.data;
assert.equal(data.name, 'DragonSlayer');
assert.equal(data.class, 'warrior');
assert.equal(data.level, 5);
assert.equal(data.kills, 42);
assert.equal(data.gold, 300);
assert.equal(data.deaths, 3);
assert.ok(typeof data.str === 'number');
assert.ok(typeof data.agi === 'number');
assert.ok(typeof data.int === 'number');
assert.ok(typeof data.vit === 'number');
assert.ok(typeof data.xp === 'number');
assert.deepStrictEqual(data.equipment, { weapon: null, armor: null, trinket: null });
// Response must NOT contain private fields.
assert.ok(!('energy' in data));
assert.ok(!('energy_updated_at' in data));
assert.ok(!('user_id' in data));
assert.ok(!('created_at' in data));
assert.ok(!('password_hash' in data));
});
it('returns equipped item names', async () => {
const cookie = await registerAndLogin(`herouser2`, 'password123');
const characterId = await createHero(cookie, 'ArmoredOne', 'mage');
// Insert starter gear directly into inventory (starter gear not auto-created yet).
const db = require('../../src/db').getDb();
// Get a weapon item from the items table.
const weapon = db.prepare(
"SELECT id FROM items WHERE type = 'weapon' AND min_level <= 1 ORDER BY id LIMIT 1"
).get();
assert.ok(weapon, 'Should have a level-1 weapon in the items table');
// Insert it into inventory, equipped as weapon.
const now = Date.now();
db.prepare(
'INSERT INTO inventory (character_id, item_id, quantity, equipped_slot, acquired_at) VALUES (?, ?, 1, ?, ?)'
).run(characterId, weapon.id, 'weapon', now);
const res = await fetch(`${baseUrl}/api/heroes/ArmoredOne`);
assert.equal(res.status, 200);
const json = await res.json();
assert.equal(json.ok, true);
assert.ok(json.data.equipment.weapon, 'Should have a weapon equipped');
});
it('is case-insensitive', async () => {
const cookie = await registerAndLogin(`herouser3`, 'password123');
await createHero(cookie, 'TeStHeRo', 'ranger');
const res1 = await fetch(`${baseUrl}/api/heroes/TeStHeRo`);
assert.equal(res1.status, 200);
const res2 = await fetch(`${baseUrl}/api/heroes/testhero`);
assert.equal(res2.status, 200);
const json1 = await res1.json();
const json2 = await res2.json();
assert.equal(json1.data.name, json2.data.name);
});
it('returns 404 for a non-existent hero', async () => {
const res = await fetch(`${baseUrl}/api/heroes/NonExistentHero99`);
assert.equal(res.status, 404);
const json = await res.json();
assert.equal(json.ok, false);
assert.equal(json.error.code, 'NOT_FOUND');
});
it('works without authentication', async () => {
// This endpoint should be accessible without any session cookie.
const cookie = await registerAndLogin(`herouser4`, 'password123');
await createHero(cookie, 'PublicHero', 'warrior');
// No cookie at all — should still work.
const res = await fetch(`${baseUrl}/api/heroes/PublicHero`);
assert.equal(res.status, 200);
const json = await res.json();
assert.equal(json.ok, true);
assert.equal(json.data.name, 'PublicHero');
});
});