Files
botfights/server/src/engine/poll-responses.test.ts
T
2026-04-11 19:46:37 +01:00

112 lines
3.5 KiB
TypeScript

import { describe, it, expect, vi, afterEach } from 'vitest'
import {
waitForPollResponse,
submitPollResponse,
getPendingPollChallenge,
clearAllPendingPolls,
isPollingBot,
} from './poll-responses.js'
import type { Challenge } from './challenges.js'
const mockChallenge: Challenge = {
type: 'speed_blitz',
label: 'Speed Blitz',
prompt: 'What year was Bitcoin created?',
answers: ['2009'],
scoring: 'factual',
timeout_ms: 8000,
baseDamage: 20,
}
const mockOpponent = { name: 'TestBot', wins: 5, losses: 3 }
afterEach(() => {
clearAllPendingPolls()
vi.useRealTimers()
})
describe('isPollingBot', () => {
it('returns true for poll sentinel URL', () => {
expect(isPollingBot('http://poll.local/')).toBe(true)
})
it('returns false for other URLs', () => {
expect(isPollingBot('https://example.com/webhook')).toBe(false)
})
})
describe('waitForPollResponse + getPendingPollChallenge', () => {
it('stores challenge and makes it retrievable', () => {
void waitForPollResponse('f1', 'b1', mockChallenge, 1, mockOpponent, 'arena1', null)
const pending = getPendingPollChallenge('b1')
expect(pending).not.toBeNull()
expect(pending!.fightId).toBe('f1')
expect(pending!.type).toBe('speed_blitz')
expect(pending!.prompt).toBe('What year was Bitcoin created?')
expect(pending!.roundNumber).toBe(1)
expect(pending!.opponent.name).toBe('TestBot')
expect(pending!.arena).toBe('arena1')
expect(pending!.constraints.max_tokens).toBe(500)
})
it('returns null for unknown bot', () => {
expect(getPendingPollChallenge('nonexistent')).toBeNull()
})
})
describe('submitPollResponse', () => {
it('accepts submission and resolves promise', async () => {
const promise = waitForPollResponse('f2', 'b2', mockChallenge, 1, mockOpponent, 'arena1', null)
const accepted = submitPollResponse('b2', '2009', 'ez')
expect(accepted).toBe(true)
const result = await promise
expect(result.answer).toBe('2009')
expect(result.trashTalk).toBe('ez')
expect(result.timedOut).toBe(false)
})
it('returns false for unknown bot (no pending challenge)', () => {
expect(submitPollResponse('nobody', 'answer')).toBe(false)
})
it('rejects duplicate submission (second submit returns false)', async () => {
void waitForPollResponse('f3', 'b3', mockChallenge, 1, mockOpponent, 'arena1', null)
expect(submitPollResponse('b3', 'first')).toBe(true)
expect(submitPollResponse('b3', 'second')).toBe(false)
})
it('truncates answer to 2000 chars', async () => {
const promise = waitForPollResponse('f4', 'b4', mockChallenge, 1, mockOpponent, 'arena1', null)
submitPollResponse('b4', 'x'.repeat(3000))
const result = await promise
expect(result.answer!.length).toBe(2000)
})
})
describe('timeout', () => {
it('times out and resolves with null answer', async () => {
vi.useFakeTimers()
const shortChallenge = { ...mockChallenge, timeout_ms: 100 }
const promise = waitForPollResponse('f5', 'b5', shortChallenge, 1, mockOpponent, 'arena1', null)
// Advance past timeout (100ms + POLL_GRACE_MS=10_000 = 10_100ms)
vi.advanceTimersByTime(10_200)
const result = await promise
expect(result.timedOut).toBe(true)
expect(result.answer).toBeNull()
})
it('clears pending after timeout', async () => {
vi.useFakeTimers()
const shortChallenge = { ...mockChallenge, timeout_ms: 100 }
void waitForPollResponse('f6', 'b6', shortChallenge, 1, mockOpponent, 'arena1', null)
vi.advanceTimersByTime(10_200)
expect(getPendingPollChallenge('b6')).toBeNull()
})
})