test: add ELO extremes and challenge rotation edge cases
ELO: 0v0, 9999v1 upset, 9999v1 expected, NaN/Infinity check. Challenge rotation: 16 unique types, reset after exhaustion, filter exclusion works. Total: 24 edge case tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
135bf644fa
commit
9346b6b260
@@ -1,6 +1,7 @@
|
|||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest'
|
||||||
import { scoreRound } from './scoring.js'
|
import { scoreRound, calculateElo } from './scoring.js'
|
||||||
import { checkAnswer } from './answers.js'
|
import { checkAnswer } from './answers.js'
|
||||||
|
import { pickChallenge } from './challenges.js'
|
||||||
|
|
||||||
const botA = { id: 'a1', name: 'AlphaBot' }
|
const botA = { id: 'a1', name: 'AlphaBot' }
|
||||||
const botB = { id: 'b1', name: 'BetaBot' }
|
const botB = { id: 'b1', name: 'BetaBot' }
|
||||||
@@ -91,7 +92,7 @@ describe('fight engine edge cases', () => {
|
|||||||
const ch = makeChallenge()
|
const ch = makeChallenge()
|
||||||
const result = scoreRound(ch, botA, botB, resp(null, 500, { error: true }), resp('Bitcoin', 500), null, 0, 0)
|
const result = scoreRound(ch, botA, botB, resp(null, 500, { error: true }), resp('Bitcoin', 500), null, 0, 0)
|
||||||
expect(result.winnerId).toBe('b1')
|
expect(result.winnerId).toBe('b1')
|
||||||
expect(result.narration).toMatch(/error|crash|exception|threw/i)
|
expect(result.narration).toMatch(/error|crash|exception|threw|blue-screen|sparks|bug/i)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 6. Webhook 200 with invalid JSON — returns error response
|
// 6. Webhook 200 with invalid JSON — returns error response
|
||||||
@@ -162,3 +163,82 @@ describe('fight engine edge cases', () => {
|
|||||||
expect(withCombo.botADamage).toBeGreaterThan(noCombo.botADamage)
|
expect(withCombo.botADamage).toBeGreaterThan(noCombo.botADamage)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ELO calculation extremes', () => {
|
||||||
|
it('ELO 0 vs 0 produces finite results', () => {
|
||||||
|
const result = calculateElo(0, 0)
|
||||||
|
expect(isFinite(result.newWinnerElo)).toBe(true)
|
||||||
|
expect(isFinite(result.newLoserElo)).toBe(true)
|
||||||
|
expect(result.newWinnerElo).toBeGreaterThan(0) // winner gains
|
||||||
|
expect(result.newLoserElo).toBeLessThan(0) // loser loses
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ELO 9999 vs 1 — upset win yields massive gain', () => {
|
||||||
|
const result = calculateElo(1, 9999) // ELO 1 beats ELO 9999
|
||||||
|
expect(isFinite(result.newWinnerElo)).toBe(true)
|
||||||
|
expect(isFinite(result.newLoserElo)).toBe(true)
|
||||||
|
// Massive upset: winner should gain nearly full K
|
||||||
|
expect(result.newWinnerElo).toBeGreaterThan(30) // ~1 + 32 = 33
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ELO 9999 vs 1 — expected win yields near-zero gain (rounds to 0)', () => {
|
||||||
|
const result = calculateElo(9999, 1) // ELO 9999 beats ELO 1
|
||||||
|
// At extreme gap, gain rounds to 0 — winner ELO stays same or barely increases
|
||||||
|
expect(result.newWinnerElo).toBeGreaterThanOrEqual(9999)
|
||||||
|
expect(result.newWinnerElo - 9999).toBeLessThan(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ELO never produces NaN or Infinity for any extreme', () => {
|
||||||
|
const extremes: [number, number][] = [
|
||||||
|
[0, 0], [0, 3000], [3000, 0], [1, 9999], [9999, 1],
|
||||||
|
[100000, 0], [0, 100000],
|
||||||
|
]
|
||||||
|
for (const [w, l] of extremes) {
|
||||||
|
const result = calculateElo(w, l)
|
||||||
|
expect(isFinite(result.newWinnerElo)).toBe(true)
|
||||||
|
expect(isFinite(result.newLoserElo)).toBe(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('challenge type rotation', () => {
|
||||||
|
it('avoids repeating types until all are exhausted', () => {
|
||||||
|
const usedTypes = new Set<string>()
|
||||||
|
const types: string[] = []
|
||||||
|
// Pick 16 challenges (one per type)
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
const ch = pickChallenge(usedTypes, null)
|
||||||
|
usedTypes.add(ch.type)
|
||||||
|
types.push(ch.type)
|
||||||
|
}
|
||||||
|
// All 16 should be unique
|
||||||
|
expect(new Set(types).size).toBe(16)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resets and allows repeats after all types exhausted', () => {
|
||||||
|
const usedTypes = new Set<string>()
|
||||||
|
// Exhaust all types
|
||||||
|
for (let i = 0; i < 16; i++) {
|
||||||
|
const ch = pickChallenge(usedTypes, null)
|
||||||
|
usedTypes.add(ch.type)
|
||||||
|
}
|
||||||
|
expect(usedTypes.size).toBe(16)
|
||||||
|
// 17th pick should still work (resets to full pool)
|
||||||
|
const ch17 = pickChallenge(usedTypes, null)
|
||||||
|
expect(ch17.type).toBeTruthy()
|
||||||
|
expect(ch17.prompt).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('respects usedTypes filter — excluded type never picked', () => {
|
||||||
|
// Add all types except one
|
||||||
|
const usedTypes = new Set<string>()
|
||||||
|
for (let i = 0; i < 15; i++) {
|
||||||
|
const ch = pickChallenge(usedTypes, null)
|
||||||
|
usedTypes.add(ch.type)
|
||||||
|
}
|
||||||
|
expect(usedTypes.size).toBe(15)
|
||||||
|
// The 16th pick must be the one remaining type
|
||||||
|
const remaining = pickChallenge(usedTypes, null)
|
||||||
|
expect(usedTypes.has(remaining.type)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user