'use strict' const { test, describe, before, after } = require('node:test') const assert = require('node:assert/strict') const { initDb } = require('../db.js') let db before(() => { db = initDb(':memory:') }) after(() => { db._db.close() }) describe('profiles', () => { test('createProfile creates a profile', () => { const p = db.createProfile('test@example.com', 'Tester') assert.equal(p.email, 'test@example.com') assert.equal(p.display_name, 'Tester') assert.ok(p.id) }) test('findProfileByEmail returns the profile', () => { const p = db.findProfileByEmail('test@example.com') assert.equal(p.email, 'test@example.com') }) test('findProfileByEmail returns null for unknown email', () => { assert.equal(db.findProfileByEmail('nobody@example.com'), null) }) test('findProfileById returns profile by id', () => { const created = db.createProfile('byid@example.com', 'ById') const found = db.findProfileById(created.id) assert.equal(found.id, created.id) assert.equal(found.email, 'byid@example.com') }) test('findProfileById returns null for unknown id', () => { assert.equal(db.findProfileById(999999), null) }) test('updateProfileSeen updates last_seen timestamp', () => { const created = db.createProfile('seen@example.com', 'Seen') const before = created.last_seen // Ensure time advances by at least 1ms const laterTime = before + 1 // Use a small delay approach: manipulate via direct SQL isn't needed, // just call updateProfileSeen and verify last_seen >= original db.updateProfileSeen(created.id) const updated = db.findProfileById(created.id) assert.ok(updated.last_seen >= before) }) }) describe('magic tokens', () => { let profileId before(() => { profileId = db.createProfile('magic@example.com', 'Magic').id }) test('createMagicToken and useMagicToken returns profileId', () => { const expires = Date.now() + 86400000 db.createMagicToken('tok123', profileId, expires) const result = db.useMagicToken('tok123') assert.equal(result, profileId) }) test('useMagicToken returns null for used token', () => { assert.equal(db.useMagicToken('tok123'), null) }) test('useMagicToken returns null for expired token', () => { db.createMagicToken('expiredtok', profileId, Date.now() - 1000) assert.equal(db.useMagicToken('expiredtok'), null) }) }) describe('sessions', () => { let profileId before(() => { profileId = db.createProfile('session@example.com', 'Session').id }) test('createSession and getSession returns profile_id', () => { db.createSession('sess1', profileId) const s = db.getSession('sess1') assert.equal(s.profile_id, profileId) }) test('getSession returns null for unknown token', () => { assert.equal(db.getSession('unknown'), null) }) test('deleteSession removes session', () => { db.deleteSession('sess1') assert.equal(db.getSession('sess1'), null) }) }) describe('progress', () => { let profileId before(() => { profileId = db.createProfile('progress@example.com', 'Progress').id }) test('getOrCreateProgress returns default progress', () => { const p = db.getOrCreateProgress(profileId) assert.equal(p.level, 1) assert.equal(p.score, 0) assert.equal(p.streak, 0) }) test('recordAnswer increments score and streak on correct', () => { db.recordAnswer(profileId, 'A', true) const p = db.getOrCreateProgress(profileId) assert.ok(p.score > 0) assert.equal(p.streak, 1) assert.equal(p.total_correct, 1) assert.equal(p.total_attempts, 1) }) test('recordAnswer resets streak on incorrect', () => { db.recordAnswer(profileId, 'A', false) const p = db.getOrCreateProgress(profileId) assert.equal(p.streak, 0) assert.equal(p.total_attempts, 2) }) test('updateLevel changes the level', () => { db.updateLevel(profileId, 5) const p = db.getOrCreateProgress(profileId) assert.equal(p.level, 5) }) test('updateLevel works even when no progress row exists yet', () => { const newProfile = db.createProfile('updatelevel@example.com', 'UpdateLevel') db.updateLevel(newProfile.id, 3) const p = db.getOrCreateProgress(newProfile.id) assert.equal(p.level, 3) }) }) describe('mnemonics', () => { let profileId before(() => { profileId = db.createProfile('mnemonic@example.com', 'Mnemonic').id }) test('saveMnemonic and getMnemonics returns map', () => { db.saveMnemonic(profileId, 'A', 'go WALKING') db.saveMnemonic(profileId, 'E', 'go') const m = db.getMnemonics(profileId) assert.equal(m.A, 'go WALKING') assert.equal(m.E, 'go') }) }) describe('letter stats', () => { let profileId before(() => { profileId = db.createProfile('stats@example.com', 'Stats').id }) test('recordAnswer tracks letter stats', () => { db.recordAnswer(profileId, 'S', true) db.recordAnswer(profileId, 'S', false) const stats = db.getLetterStats(profileId) assert.equal(stats.S.correct, 1) assert.equal(stats.S.attempts, 2) }) }) describe('admin', () => { before(() => { const p1 = db.createProfile('admin1@example.com', 'Admin1') const p2 = db.createProfile('admin2@example.com', 'Admin2') db.recordAnswer(p1.id, 'A', true) db.recordAnswer(p2.id, 'B', false) db.recordAnswer(p2.id, 'B', false) }) test('getAdminUsers returns users list', () => { const users = db.getAdminUsers() assert.ok(Array.isArray(users)) assert.ok(users.length >= 2) // Each user should have email and id const emails = users.map(u => u.email) assert.ok(emails.includes('admin1@example.com')) assert.ok(emails.includes('admin2@example.com')) }) test('getAdminStats returns totalUsers, avgScore, mostMissed', () => { const stats = db.getAdminStats() assert.ok(typeof stats.totalUsers === 'number') assert.ok(stats.totalUsers >= 2) assert.ok(typeof stats.avgScore === 'number') assert.ok(Array.isArray(stats.mostMissed)) // B was missed twice — should appear in mostMissed const missedLetters = stats.mostMissed.map(m => m.letter) assert.ok(missedLetters.includes('B')) }) })