161 lines
5.8 KiB
JavaScript
161 lines
5.8 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: 'Rusty Sword', 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');
|
|
|
|
// Mage already has Apprentice Staff (item 2) equipped from starter kit.
|
|
// Insert an armor item and equip it to test equipped item names.
|
|
const db = require('../../src/db').getDb();
|
|
|
|
// Get an armor item from the items table.
|
|
const armor = db.prepare(
|
|
"SELECT id FROM items WHERE type = 'armor' AND min_level <= 1 ORDER BY id LIMIT 1"
|
|
).get();
|
|
assert.ok(armor, 'Should have a level-1 armor in the items table');
|
|
|
|
// Insert it into inventory, equipped as armor.
|
|
const now = Date.now();
|
|
db.prepare(
|
|
'INSERT INTO inventory (character_id, item_id, quantity, equipped_slot, acquired_at) VALUES (?, ?, 1, ?, ?)'
|
|
).run(characterId, armor.id, 'armor', 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');
|
|
assert.ok(json.data.equipment.armor, 'Should have armor 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');
|
|
});
|
|
});
|