b229e6871f
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
217 lines
6.3 KiB
JavaScript
217 lines
6.3 KiB
JavaScript
'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 callTime = Date.now()
|
|
db.updateProfileSeen(created.id)
|
|
const updated = db.findProfileById(created.id)
|
|
assert.ok(updated.last_seen >= callTime)
|
|
})
|
|
})
|
|
|
|
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')
|
|
})
|
|
|
|
test('saveMnemonic awards 50 pts on first save, not on update', () => {
|
|
const p1 = db.getOrCreateProgress(profileId)
|
|
db.saveMnemonic(profileId, 'T', 'dash') // first save → +50 pts
|
|
const p2 = db.getOrCreateProgress(profileId)
|
|
assert.equal(p2.score, p1.score + 50)
|
|
|
|
db.saveMnemonic(profileId, 'T', 'updated dash') // update → no extra pts
|
|
const p3 = db.getOrCreateProgress(profileId)
|
|
assert.equal(p3.score, p2.score)
|
|
})
|
|
})
|
|
|
|
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'))
|
|
})
|
|
})
|