import test from 'brittle'
import { ChallengeManager } from '../../lib/auth/challenge-manager.js'
import { MockRegistry } from '../helpers/fixtures.js'
test('ChallengeManager - generate challenge', async (t) => {
const mgr = new ChallengeManager({ ttl: 60000 })
const result = await mgr.generate({ context: 'test' })
t.ok(result.challengeId, 'has challengeId')
t.ok(result.challenge, 'has challenge string')
t.ok(result.expiresAt > Date.now(), 'expires in the future')
mgr.destroy()
})
test('ChallengeManager - verify valid challenge', async (t) => {
const mgr = new ChallengeManager({ ttl: 60000 })
const result = await mgr.generate()
const valid = await mgr.verify(result.challengeId, result.challenge)
t.ok(valid, 'challenge verifies')
mgr.destroy()
})
test('ChallengeManager - consume prevents replay', async (t) => {
const mgr = new ChallengeManager({ ttl: 60000 })
const result = await mgr.generate()
const consumed = await mgr.consume(result.challengeId)
t.ok(consumed, 'consumed successfully')
// Try to consume again
const replayGet = await mgr.get(result.challengeId)
t.absent(replayGet, 'consumed challenge returns null on get')
mgr.destroy()
})
test('ChallengeManager - expired challenge rejected', async (t) => {
const mgr = new ChallengeManager({ ttl: 1 }) // 1ms TTL
const result = await mgr.generate()
// Wait for expiry
await new Promise(resolve => setTimeout(resolve, 10))
const record = await mgr.get(result.challengeId)
t.absent(record, 'expired challenge returns null')
mgr.destroy()
})
test('ChallengeManager - verify wrong challenge string fails', async (t) => {
const mgr = new ChallengeManager({ ttl: 60000 })
const result = await mgr.generate()
const valid = await mgr.verify(result.challengeId, 'wrong-challenge-string')
t.absent(valid, 'wrong challenge string fails')
mgr.destroy()
})
test('ChallengeManager - verify unknown challengeId fails', async (t) => {
const mgr = new ChallengeManager({ ttl: 60000 })
const valid = await mgr.verify('nonexistent-id', 'whatever')
t.absent(valid, 'unknown challengeId fails')
mgr.destroy()
})
test('ChallengeManager - with persistent storage', async (t) => {
const db = new MockRegistry()
const mgr = new ChallengeManager({ ttl: 60000, db })
const result = await mgr.generate()
// Should be stored in db
const entry = await db.get(`challenge:${result.challengeId}`)
t.ok(entry, 'challenge stored in db')
t.is(entry.value.challenge, result.challenge, 'stored challenge matches')
mgr.destroy()
})
test('ChallengeManager - stats', async (t) => {
const mgr = new ChallengeManager({ ttl: 60000 })
await mgr.generate()
await mgr.generate()
const result3 = await mgr.generate()
await mgr.consume(result3.challengeId)
const stats = await mgr.stats()
t.is(stats.total, 3, 'total challenges')
t.is(stats.active, 2, 'active challenges')
t.is(stats.used, 1, 'used challenges')
mgr.destroy()
})