import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { getGameDb } from '@/lib/db/client'; import { verifyPassword } from '@/lib/auth'; import { STARTING_CASH, STARTING_REPUTATION } from '@/lib/game/seed'; import { POST } from './route'; // v1.1.0 (ISS-107) — register route tests. Same temp-DB pattern as the login route tests: the DB // singleton points at a throwaway file, and every created user is verified through verifyPassword(). let dir: string; beforeAll(async () => { dir = await fs.mkdtemp(path.join(os.tmpdir(), 'flight-register-api-')); process.env.FLIGHT_DB_PATH = path.join(dir, 'register.db'); }); afterAll(async () => { delete process.env.FLIGHT_DB_PATH; await fs.rm(dir, { recursive: true, force: true }); }); const post = (body: unknown) => POST(new Request('http://localhost/api/register', { method: 'POST', body: JSON.stringify(body) })); describe('POST /api/register', () => { it('rejects malformed bodies and bad credentials with 400', async () => { for (const body of [ {}, // missing both fields { username: 'ab', password: 'longenough' }, // too short { username: 'a'.repeat(21), password: 'longenough' }, // too long { username: 'bad user!', password: 'longenough' }, // bad charset (space + !) { username: 'validuser', password: 'short' }, // password < 8 chars { username: 'validuser', password: 'longenough', bogus: 1 }, // unknown field ]) { const res = await post(body); expect(res.status).toBe(400); expect(((await res.json()) as { error: string }).error).toBeTruthy(); } }); it('creates users + players rows sharing one id, with the starter economy, and mints a session', async () => { const res = await post({ username: 'NewUser42', password: 'correct-horse-1' }); expect(res.status).toBe(200); const body = (await res.json()) as { sessionId: string; user: { username: string; admin: boolean } }; expect(body.user).toEqual({ username: 'newuser42', admin: false }); // input lowercased expect(body.sessionId).toMatch(/^[0-9a-f-]{36}$/); const inst = await getGameDb(); const userRow = inst.raw.exec('SELECT id, password_hash FROM users WHERE username = ?', ['newuser42'])[0].values[0]; const playerRow = inst.raw.exec('SELECT id, provider, cash, reputation FROM players WHERE username = ?', ['newuser42'])[0].values[0]; expect(String(playerRow[0])).toBe(String(userRow[0])); // one UUID for both rows expect(verifyPassword('correct-horse-1', String(userRow[1]))).toBe(true); expect(String(playerRow[1])).toBe('local'); expect(Number(playerRow[2])).toBe(STARTING_CASH); expect(Number(playerRow[3])).toBe(STARTING_REPUTATION); // The minted session resolves to the new user. const sess = inst.raw.exec('SELECT user_id FROM sessions WHERE id = ?', [body.sessionId])[0].values[0]; expect(String(sess[0])).toBe(String(userRow[0])); }); it('returns 409 when the username is taken (case-insensitively)', async () => { await post({ username: 'taken-user', password: 'correct-horse-1' }); const res = await post({ username: 'Taken-User', password: 'different-pass-2' }); expect(res.status).toBe(409); expect(((await res.json()) as { error: string }).error).toMatch(/already taken/i); }); });