test: add 65 server engine tests — answers, scoring, challenges
- answers.test.ts: 19 tests covering all 10 checkAnswer() tiers - scoring.test.ts: 30 tests for scoreRound, calculateElo, calculateTier, applyModifiers - challenges.test.ts: 16 tests for pickChallenge, type exclusion, data integrity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
b2d1a2da02
commit
f837153771
@@ -0,0 +1,146 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { pickChallenge, getAllChallengeTypes, getAnswerPool } from './challenges.js'
|
||||
|
||||
describe('pickChallenge', () => {
|
||||
it('returns a valid challenge', () => {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
expect(c).toBeTruthy()
|
||||
expect(c.type).toBeTruthy()
|
||||
expect(c.label).toBeTruthy()
|
||||
expect(c.prompt).toBeTruthy()
|
||||
expect(c.timeout_ms).toBeGreaterThan(0)
|
||||
expect(c.baseDamage).toBeGreaterThan(0)
|
||||
expect(['factual', 'creative']).toContain(c.scoring)
|
||||
})
|
||||
|
||||
it('avoids used types when possible', () => {
|
||||
const types = getAllChallengeTypes()
|
||||
// Use all types except one
|
||||
const used = new Set(types.slice(0, -1))
|
||||
const remaining = types[types.length - 1]
|
||||
// With most types used, should pick from the remaining
|
||||
// Run multiple times to account for randomness
|
||||
let gotRemaining = false
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const c = pickChallenge(used, null)
|
||||
if (c.type === remaining) gotRemaining = true
|
||||
}
|
||||
expect(gotRemaining).toBe(true)
|
||||
})
|
||||
|
||||
it('falls back to all types when all used', () => {
|
||||
const allUsed = new Set(getAllChallengeTypes())
|
||||
const c = pickChallenge(allUsed, null)
|
||||
// Should still return something
|
||||
expect(c).toBeTruthy()
|
||||
expect(c.prompt).toBeTruthy()
|
||||
})
|
||||
|
||||
it('factual challenges have answers', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
if (c.scoring === 'factual') {
|
||||
expect(c.answers).toBeTruthy()
|
||||
expect(c.answers!.length).toBeGreaterThan(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('creative challenges have no answers', () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
if (c.scoring === 'creative') {
|
||||
expect(!c.answers || c.answers.length === 0).toBe(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('factual challenges with choices have shuffled choices', () => {
|
||||
const orders = new Set<string>()
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
if (c.scoring === 'factual' && c.choices) {
|
||||
orders.add(c.choices.join(','))
|
||||
}
|
||||
}
|
||||
// With 50 tries, choices should appear in more than one order
|
||||
expect(orders.size).toBeGreaterThan(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAllChallengeTypes', () => {
|
||||
it('returns 16 types', () => {
|
||||
const types = getAllChallengeTypes()
|
||||
expect(types.length).toBe(16)
|
||||
})
|
||||
|
||||
it('types are unique', () => {
|
||||
const types = getAllChallengeTypes()
|
||||
expect(new Set(types).size).toBe(types.length)
|
||||
})
|
||||
|
||||
it('includes known types', () => {
|
||||
const types = getAllChallengeTypes()
|
||||
expect(types).toContain('speed_blitz')
|
||||
expect(types).toContain('roast_battle')
|
||||
expect(types).toContain('riddle')
|
||||
expect(types).toContain('math_blitz')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAnswerPool', () => {
|
||||
it('returns answers for factual types', () => {
|
||||
const pool = getAnswerPool('speed_blitz')
|
||||
expect(pool.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('returns empty array for creative types', () => {
|
||||
const pool = getAnswerPool('roast_battle')
|
||||
expect(pool.length).toBe(0)
|
||||
})
|
||||
|
||||
it('returns empty for unknown type', () => {
|
||||
const pool = getAnswerPool('nonexistent_type')
|
||||
expect(pool.length).toBe(0)
|
||||
})
|
||||
|
||||
it('answers are unique within pool', () => {
|
||||
const types = getAllChallengeTypes()
|
||||
for (const type of types) {
|
||||
const pool = getAnswerPool(type)
|
||||
const unique = new Set(pool)
|
||||
expect(unique.size).toBe(pool.length)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('prompt data integrity', () => {
|
||||
it('all 800 prompts are accessible via pickChallenge', () => {
|
||||
// Run enough picks to verify the system works
|
||||
const seenPrompts = new Set<string>()
|
||||
for (let i = 0; i < 500; i++) {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
seenPrompts.add(c.prompt)
|
||||
}
|
||||
// Should have seen a good variety
|
||||
expect(seenPrompts.size).toBeGreaterThan(50)
|
||||
})
|
||||
|
||||
it('no empty prompts', () => {
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
expect(c.prompt.trim().length).toBeGreaterThan(5)
|
||||
}
|
||||
})
|
||||
|
||||
it('factual prompts have non-empty answers', () => {
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const c = pickChallenge(new Set(), null)
|
||||
if (c.scoring === 'factual' && c.answers) {
|
||||
for (const a of c.answers) {
|
||||
expect(a.trim().length).toBeGreaterThan(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user