Files
botfights/server/src/engine/mock.test.ts
T
DorianandClaude Opus 4.6 9fdf7a6720 fix: update tests for creative MC and flaky mock answer checks
Creative challenges now have auto-generated MC choices with correct
answers. Mock bad answers can be empty at any elo, so test checks
proportion instead of requiring all non-empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 08:38:53 +00:00

153 lines
5.2 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { mockResponse } from './mock.js'
import { pickChallenge, getAllChallengeTypes } from './challenges.js'
describe('mockResponse', () => {
it('returns a valid response structure', () => {
const challenge = pickChallenge(new Set(), null)
const resp = mockResponse(challenge, 'confident', 1500)
expect(resp).toHaveProperty('answer')
expect(resp).toHaveProperty('trashTalk')
expect(resp).toHaveProperty('timeMs')
expect(resp).toHaveProperty('timedOut')
expect(resp).toHaveProperty('error')
expect(typeof resp.timeMs).toBe('number')
expect(typeof resp.timedOut).toBe('boolean')
expect(typeof resp.error).toBe('boolean')
})
it('high ELO bots answer correctly more often (factual)', () => {
let highCorrect = 0
let lowCorrect = 0
const runs = 200
for (let i = 0; i < runs; i++) {
const challenge = pickChallenge(new Set(), null)
if (challenge.scoring !== 'factual' || !challenge.answers?.length) continue
const highResp = mockResponse(challenge, 'confident', 1900)
const lowResp = mockResponse(challenge, 'clueless', 900)
if (!highResp.timedOut && !highResp.error && challenge.answers.includes(highResp.answer)) highCorrect++
if (!lowResp.timedOut && !lowResp.error && challenge.answers.includes(lowResp.answer)) lowCorrect++
}
// High ELO should answer correctly more often
expect(highCorrect).toBeGreaterThan(lowCorrect)
})
it('low ELO bots time out / error more often', () => {
let highFails = 0
let lowFails = 0
const runs = 500
for (let i = 0; i < runs; i++) {
const challenge = pickChallenge(new Set(), null)
const highResp = mockResponse(challenge, 'confident', 1900)
const lowResp = mockResponse(challenge, 'clueless', 900)
if (highResp.timedOut || highResp.error) highFails++
if (lowResp.timedOut || lowResp.error) lowFails++
}
expect(lowFails).toBeGreaterThan(highFails)
})
it('high ELO bots respond faster on average', () => {
let highTotal = 0
let lowTotal = 0
let highCount = 0
let lowCount = 0
const runs = 200
for (let i = 0; i < runs; i++) {
const challenge = pickChallenge(new Set(), null)
const highResp = mockResponse(challenge, 'confident', 1900)
const lowResp = mockResponse(challenge, 'clueless', 900)
if (!highResp.timedOut && !highResp.error) { highTotal += highResp.timeMs; highCount++ }
if (!lowResp.timedOut && !lowResp.error) { lowTotal += lowResp.timeMs; lowCount++ }
}
const highAvg = highTotal / Math.max(highCount, 1)
const lowAvg = lowTotal / Math.max(lowCount, 1)
expect(highAvg).toBeLessThan(lowAvg)
})
it('creative challenges return answers (most non-empty at high elo)', () => {
let nonEmpty = 0
let total = 0
for (let i = 0; i < 50; i++) {
const challenge = pickChallenge(new Set(), null)
if (challenge.scoring !== 'creative') continue
const resp = mockResponse(challenge, 'witty', 1800)
if (!resp.timedOut && !resp.error) {
total++
if (resp.answer.length > 0) nonEmpty++
}
}
// At elo 1800, bad answer chance is very low; most should be non-empty
expect(nonEmpty).toBeGreaterThan(total * 0.7)
})
it('trash talk is always a string', () => {
for (let i = 0; i < 20; i++) {
const challenge = pickChallenge(new Set(), null)
const resp = mockResponse(challenge, 'confident', 1900)
expect(typeof resp.trashTalk).toBe('string')
}
})
it('timeMs is always positive', () => {
for (let i = 0; i < 50; i++) {
const challenge = pickChallenge(new Set(), null)
const resp = mockResponse(challenge, 'confident', 1500)
expect(resp.timeMs).toBeGreaterThan(0)
}
})
it('produces valid responses for all 16 challenge types', () => {
const types = getAllChallengeTypes()
expect(types.length).toBe(16)
for (const type of types) {
// Force pick a challenge of this type by using all other types
const otherTypes = new Set(types.filter(t => t !== type))
const challenge = pickChallenge(otherTypes, null)
// May not get exact type if creative/factual split causes fallback, but should still work
const resp = mockResponse(challenge, 'confident', 1500)
expect(resp).toHaveProperty('answer')
expect(resp).toHaveProperty('timeMs')
expect(resp.timeMs).toBeGreaterThan(0)
}
})
it('answer is empty string on timeout', () => {
// Run many times with very low elo to trigger timeouts
let foundTimeout = false
for (let i = 0; i < 200; i++) {
const challenge = pickChallenge(new Set(), null)
const resp = mockResponse(challenge, 'clueless', 500)
if (resp.timedOut) {
expect(resp.answer).toBe('')
foundTimeout = true
}
}
expect(foundTimeout).toBe(true)
})
it('answer is empty string on error', () => {
let foundError = false
for (let i = 0; i < 200; i++) {
const challenge = pickChallenge(new Set(), null)
const resp = mockResponse(challenge, 'clueless', 500)
if (resp.error) {
expect(resp.answer).toBe('')
foundError = true
}
}
expect(foundError).toBe(true)
})
})