test: add unit tests for challenge distribution, ranked challenges, answer edge cases, and mock bot coverage

Covers plan section 4.1: pickChallenge 70/30 distribution, pickRankedChallenge never returns choices,
True/False auto-generation, special characters and long answers, all 16 challenge types produce valid
mock responses, and timeout/error answer verification. 113 → 125 tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 07:42:55 +00:00
co-authored by Claude Opus 4.6
parent 46d560af24
commit 5007d009fe
3 changed files with 127 additions and 2 deletions
+59 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { pickChallenge, getAllChallengeTypes, getAnswerPool } from './challenges.js'
import { pickChallenge, pickRankedChallenge, getAllChallengeTypes, getAnswerPool } from './challenges.js'
describe('pickChallenge', () => {
it('returns a valid challenge', () => {
@@ -66,6 +66,64 @@ describe('pickChallenge', () => {
// With 50 tries, choices should appear in more than one order
expect(orders.size).toBeGreaterThan(1)
})
it('distribution: ~70% factual, ~30% creative over many picks', () => {
let factual = 0
let creative = 0
const runs = 1000
for (let i = 0; i < runs; i++) {
const c = pickChallenge(new Set(), null)
if (c.scoring === 'factual') factual++
else creative++
}
const factualPct = factual / runs
// Allow ±10% tolerance due to randomness
expect(factualPct).toBeGreaterThan(0.55)
expect(factualPct).toBeLessThan(0.85)
})
it('True/False auto-generation for boolean answers', () => {
// Pick many challenges, find ones with answers = ['true'] or ['false']
let foundTFWithChoices = false
for (let i = 0; i < 500; i++) {
const c = pickChallenge(new Set(), null)
if (c.answers?.length === 1 && ['true', 'false'].includes(c.answers[0].toLowerCase())) {
expect(c.choices).toBeTruthy()
expect(c.choices!.length).toBe(2)
expect(c.choices!.sort()).toEqual(['False', 'True'])
foundTFWithChoices = true
}
}
expect(foundTFWithChoices).toBe(true)
})
})
describe('pickRankedChallenge', () => {
it('never returns choices', () => {
for (let i = 0; i < 100; i++) {
const c = pickRankedChallenge(new Set())
expect(c.choices).toBeUndefined()
}
})
it('returns valid challenge structure', () => {
const c = pickRankedChallenge(new Set())
expect(c.type).toBeTruthy()
expect(c.prompt).toBeTruthy()
expect(c.timeout_ms).toBeGreaterThan(0)
expect(c.baseDamage).toBeGreaterThan(0)
})
it('avoids used types', () => {
const types = getAllChallengeTypes()
const used = new Set(types.slice(0, -1))
let gotRemaining = false
for (let i = 0; i < 50; i++) {
const c = pickRankedChallenge(used)
if (c.type === types[types.length - 1]) gotRemaining = true
}
expect(gotRemaining).toBe(true)
})
})
describe('getAllChallengeTypes', () => {