153 lines
5.9 KiB
JavaScript
153 lines
5.9 KiB
JavaScript
// Step 10.16: Leaderboard public + hero profile public — no login required.
|
|
const { describe, it, before, after } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { setup, teardown, cleanup } = require('../helpers');
|
|
|
|
describe('step 10.16 — leaderboard & hero profile public', () => {
|
|
let baseUrl;
|
|
|
|
before(async () => {
|
|
baseUrl = await setup();
|
|
});
|
|
|
|
after(async () => {
|
|
await teardown();
|
|
cleanup();
|
|
});
|
|
|
|
// ─── Leaderboard: anonymous access ────────────────────────────────────
|
|
|
|
it('anonymous GET /api/leaderboard?type=level returns 200', async () => {
|
|
const res = await fetch(`${baseUrl}/api/leaderboard?type=level`);
|
|
assert.equal(res.status, 200);
|
|
const json = await res.json();
|
|
assert.equal(json.ok, true);
|
|
assert.ok(Array.isArray(json.data.entries));
|
|
assert.ok(json.data.pagination);
|
|
});
|
|
|
|
it('anonymous GET /api/leaderboard?type=gold returns 200', async () => {
|
|
const res = await fetch(`${baseUrl}/api/leaderboard?type=gold`);
|
|
assert.equal(res.status, 200);
|
|
const json = await res.json();
|
|
assert.equal(json.ok, true);
|
|
});
|
|
|
|
it('anonymous GET /api/leaderboard?type=kills returns 200', async () => {
|
|
const res = await fetch(`${baseUrl}/api/leaderboard?type=kills`);
|
|
assert.equal(res.status, 200);
|
|
const json = await res.json();
|
|
assert.equal(json.ok, true);
|
|
});
|
|
|
|
it('leaderboard response contains no user id / username / password', async () => {
|
|
const res = await fetch(`${baseUrl}/api/leaderboard?type=level`);
|
|
assert.equal(res.status, 200);
|
|
const json = await res.json();
|
|
|
|
for (const entry of json.data.entries) {
|
|
assert.ok(!('id' in entry), `entry should not have 'id': ${entry.name}`);
|
|
assert.ok(!('username' in entry), `entry should not have 'username': ${entry.name}`);
|
|
assert.ok(!('password' in entry), `entry should not have 'password': ${entry.name}`);
|
|
assert.ok(!('password_hash' in entry), `entry should not have 'password_hash': ${entry.name}`);
|
|
assert.ok(!('user_id' in entry), `entry should not have 'user_id': ${entry.name}`);
|
|
}
|
|
});
|
|
|
|
// ─── Leaderboard: validation ──────────────────────────────────────────
|
|
|
|
it('rejects SQL injection in type with 400', async () => {
|
|
const res = await fetch(
|
|
`${baseUrl}/api/leaderboard?type=level%20OR%201=1`,
|
|
);
|
|
const body = await res.json();
|
|
assert.equal(res.status, 400);
|
|
assert.equal(body.ok, false);
|
|
assert.equal(body.error.code, 'VALIDATION_ERROR');
|
|
});
|
|
|
|
it('rejects page=-1 with 400', async () => {
|
|
const res = await fetch(`${baseUrl}/api/leaderboard?type=level&page=-1`);
|
|
const body = await res.json();
|
|
assert.equal(res.status, 400);
|
|
assert.equal(body.ok, false);
|
|
assert.equal(body.error.code, 'VALIDATION_ERROR');
|
|
});
|
|
|
|
it('rejects page=abc with 400', async () => {
|
|
const res = await fetch(`${baseUrl}/api/leaderboard?type=level&page=abc`);
|
|
const body = await res.json();
|
|
assert.equal(res.status, 400);
|
|
assert.equal(body.ok, false);
|
|
assert.equal(body.error.code, 'VALIDATION_ERROR');
|
|
});
|
|
|
|
// ─── Hero profile: anonymous access (already public, verify) ──────────
|
|
|
|
it('anonymous GET /api/heroes/:name returns 200', async () => {
|
|
// Create a hero first (fresh DB has no heroes).
|
|
const regRes = await fetch(`${baseUrl}/api/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'pubuser1', password: 'password123' }),
|
|
});
|
|
assert.equal(regRes.status, 201);
|
|
|
|
const loginRes = await fetch(`${baseUrl}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'pubuser1', password: 'password123' }),
|
|
});
|
|
assert.equal(loginRes.status, 200);
|
|
const cookie = loginRes.headers.get('set-cookie').split(';')[0];
|
|
|
|
const charRes = await fetch(`${baseUrl}/api/character`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Cookie: cookie },
|
|
body: JSON.stringify({ name: 'PublicHero16', class: 'warrior' }),
|
|
});
|
|
assert.equal(charRes.status, 201);
|
|
|
|
// Now access the profile anonymously.
|
|
const res = await fetch(`${baseUrl}/api/heroes/PublicHero16`);
|
|
assert.equal(res.status, 200);
|
|
const json = await res.json();
|
|
assert.equal(json.ok, true);
|
|
assert.equal(json.data.name, 'PublicHero16');
|
|
});
|
|
|
|
it('hero profile response contains no password / user_id', async () => {
|
|
// Create a hero first.
|
|
const regRes = await fetch(`${baseUrl}/api/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'pubuser2', password: 'password123' }),
|
|
});
|
|
assert.equal(regRes.status, 201);
|
|
|
|
const loginRes = await fetch(`${baseUrl}/api/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username: 'pubuser2', password: 'password123' }),
|
|
});
|
|
assert.equal(loginRes.status, 200);
|
|
const cookie = loginRes.headers.get('set-cookie').split(';')[0];
|
|
|
|
const charRes = await fetch(`${baseUrl}/api/character`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', Cookie: cookie },
|
|
body: JSON.stringify({ name: 'PublicHero17', class: 'mage' }),
|
|
});
|
|
assert.equal(charRes.status, 201);
|
|
|
|
// Access the profile anonymously.
|
|
const res = await fetch(`${baseUrl}/api/heroes/PublicHero17`);
|
|
assert.equal(res.status, 200);
|
|
const json = await res.json();
|
|
const data = json.data;
|
|
assert.ok(!('password' in data), 'should not have password');
|
|
assert.ok(!('password_hash' in data), 'should not have password_hash');
|
|
assert.ok(!('user_id' in data), 'should not have user_id');
|
|
});
|
|
});
|