'use strict' const { test, describe, before, after } = require('node:test') const assert = require('node:assert/strict') const http = require('node:http') const { initDb } = require('../db.js') const { createServer } = require('../server.js') function request(server, method, path, body, headers = {}) { return new Promise((resolve, reject) => { const addr = server.address() const opts = { hostname: '127.0.0.1', port: addr.port, path, method, headers: { 'Content-Type': 'application/json', ...headers }, } const req = http.request(opts, res => { let data = '' res.on('data', c => { data += c }) res.on('end', () => { let json = null try { json = JSON.parse(data) } catch {} resolve({ status: res.statusCode, headers: res.headers, body: json, raw: data }) }) }) req.on('error', reject) if (body) req.write(JSON.stringify(body)) req.end() }) } let db, server, sessionToken, profileId before(() => { db = initDb(':memory:') server = createServer(db) const p = db.createProfile('prog@example.com', 'Progress') profileId = p.id sessionToken = 'progtoken' db.createSession(sessionToken, profileId) return new Promise(resolve => server.listen(0, resolve)) }) after(() => { db._db.close() return new Promise(resolve => server.close(resolve)) }) const authHeader = () => ({ Authorization: `Bearer ${sessionToken}` }) describe('GET /api/progress', () => { test('returns progress for authed user', async () => { const r = await request(server, 'GET', '/api/progress', null, authHeader()) assert.equal(r.status, 200) assert.equal(r.body.level, 1) assert.equal(r.body.score, 0) }) test('returns 401 without auth', async () => { const r = await request(server, 'GET', '/api/progress', null) assert.equal(r.status, 401) }) }) describe('POST /api/progress/answer', () => { test('records correct answer and returns updated progress', async () => { const r = await request(server, 'POST', '/api/progress/answer', { letter: 'A', correct: true }, authHeader()) assert.equal(r.status, 200) assert.ok(r.body.score > 0) assert.equal(r.body.streak, 1) }) test('records incorrect answer (resets streak)', async () => { const r = await request(server, 'POST', '/api/progress/answer', { letter: 'A', correct: false }, authHeader()) assert.equal(r.status, 200) assert.equal(r.body.streak, 0) }) test('returns 401 without auth', async () => { const r = await request(server, 'POST', '/api/progress/answer', { letter: 'A', correct: true }) assert.equal(r.status, 401) }) test('returns 400 if letter missing', async () => { const r = await request(server, 'POST', '/api/progress/answer', { correct: true }, authHeader()) assert.equal(r.status, 400) }) test('returns 400 if correct is missing', async () => { const r = await request(server, 'POST', '/api/progress/answer', { letter: 'A' }, authHeader()) assert.equal(r.status, 400) }) test('returns 400 if correct is non-boolean', async () => { const r = await request(server, 'POST', '/api/progress/answer', { letter: 'A', correct: 'true' }, authHeader()) assert.equal(r.status, 400) }) }) describe('GET /api/mnemonics', () => { before(() => { db.saveMnemonic(profileId, 'A', 'a phrase') }) test('returns mnemonic map', async () => { const r = await request(server, 'GET', '/api/mnemonics', null, authHeader()) assert.equal(r.status, 200) assert.equal(r.body.A, 'a phrase') }) test('returns 401 without auth', async () => { const r = await request(server, 'GET', '/api/mnemonics', null) assert.equal(r.status, 401) }) }) describe('POST /api/mnemonics', () => { test('saves mnemonic and returns ok', async () => { const r = await request(server, 'POST', '/api/mnemonics', { letter: 'E', phrase: 'yes' }, authHeader()) assert.equal(r.status, 200) assert.equal(r.body.ok, true) const m = db.getMnemonics(profileId) assert.equal(m.E, 'yes') }) test('returns 400 if letter or phrase missing', async () => { const r = await request(server, 'POST', '/api/mnemonics', { letter: 'T' }, authHeader()) assert.equal(r.status, 400) }) test('returns 401 without auth', async () => { const r = await request(server, 'POST', '/api/mnemonics', { letter: 'E', phrase: 'yes' }) assert.equal(r.status, 401) }) })