const test = require('node:test'); const assert = require('node:assert/strict'); const http = require('node:http'); const { setup, teardown, cleanup } = require('../helpers'); // Simple cookie jar for tests function createJar() { return { cookies: [] }; } function jarSet(jar, res) { const cookies = res.headers['set-cookie']; if (cookies && cookies.length) { jar.cookies = cookies.map((c) => c.split(';')[0]); } } function jarGet(jar) { return jar.cookies.join('; '); } function fetchJson(base, path, options = {}) { return new Promise((resolve, reject) => { const url = new URL(path, base); const opts = { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...(options.cookie ? { Cookie: options.cookie } : {}), }, }; const req = http.request(url, opts, (res) => { let data = ''; res.on('data', (c) => { data += c; }); res.on('end', () => { resolve({ status: res.statusCode, headers: res.headers, body: data ? JSON.parse(data) : null, }); }); }); req.on('error', reject); if (options.body) req.write(JSON.stringify(options.body)); req.end(); }); } test('auth register ok', async () => { const baseUrl = await setup(); try { const res = await fetchJson(baseUrl, '/api/auth/register', { method: 'POST', body: { username: 'testuser', password: 'password123' }, }); assert.equal(res.status, 201); assert.equal(res.body.ok, true); assert.equal(res.body.data.user.username, 'testuser'); } finally { await teardown(); cleanup(); } }); test('auth duplicate username', async () => { const baseUrl = await setup(); try { await fetchJson(baseUrl, '/api/auth/register', { method: 'POST', body: { username: 'dupuser', password: 'password123' }, }); const res = await fetchJson(baseUrl, '/api/auth/register', { method: 'POST', body: { username: 'DupUser', password: 'password123' }, }); assert.equal(res.status, 409); assert.equal(res.body.error.code, 'USERNAME_TAKEN'); } finally { await teardown(); cleanup(); } }); test('auth login ok', async () => { const baseUrl = await setup(); try { await fetchJson(baseUrl, '/api/auth/register', { method: 'POST', body: { username: 'logintest', password: 'password123' }, }); const res = await fetchJson(baseUrl, '/api/auth/login', { method: 'POST', body: { username: 'logintest', password: 'password123' }, }); assert.equal(res.status, 200); assert.equal(res.body.ok, true); assert.equal(res.body.data.user.username, 'logintest'); assert.equal(res.body.data.hasHero, false); } finally { await teardown(); cleanup(); } }); test('auth login bad password', async () => { const baseUrl = await setup(); try { await fetchJson(baseUrl, '/api/auth/register', { method: 'POST', body: { username: 'badpass', password: 'password123' }, }); const res = await fetchJson(baseUrl, '/api/auth/login', { method: 'POST', body: { username: 'badpass', password: 'wrongpassword' }, }); assert.equal(res.status, 401); assert.equal(res.body.error.code, 'INVALID_CREDENTIALS'); } finally { await teardown(); cleanup(); } }); test('auth me requires session', async () => { const baseUrl = await setup(); try { const res = await fetchJson(baseUrl, '/api/auth/me'); assert.equal(res.status, 401); assert.equal(res.body.error.code, 'UNAUTHENTICATED'); } finally { await teardown(); cleanup(); } }); test('auth logout clears session', async () => { const baseUrl = await setup(); const jar = createJar(); try { // Register const regRes = await fetchJson(baseUrl, '/api/auth/register', { method: 'POST', body: { username: 'logouttest', password: 'password123' }, }); assert.equal(regRes.status, 201); jarSet(jar, regRes); // Me should work with session from register const meRes1 = await fetchJson(baseUrl, '/api/auth/me', { cookie: jarGet(jar), }); assert.equal(meRes1.status, 200); // Logout const logoutRes = await fetchJson(baseUrl, '/api/auth/logout', { method: 'POST', cookie: jarGet(jar), }); assert.equal(logoutRes.status, 200); // Me should fail after logout const meRes2 = await fetchJson(baseUrl, '/api/auth/me', { cookie: jarGet(jar), }); assert.equal(meRes2.status, 401); assert.equal(meRes2.body.error.code, 'UNAUTHENTICATED'); } finally { await teardown(); cleanup(); } });