test: add human-responses unit tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e48a984d96
commit
48847d879c
@@ -0,0 +1,88 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { waitForHumanResponse, submitHumanResponse, getPendingChallenge, clearAllPending } from './human-responses.js'
|
||||
import type { Challenge } from './challenges.js'
|
||||
|
||||
const mockChallenge: Challenge = {
|
||||
type: 'bitcoin_trivia',
|
||||
label: 'Bitcoin Trivia',
|
||||
prompt: 'What year was Bitcoin launched?',
|
||||
answers: ['2009'],
|
||||
scoring: 'factual',
|
||||
timeout_ms: 8000,
|
||||
difficulty: 'medium',
|
||||
choices: ['2009', '2010', '2008'],
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
clearAllPending()
|
||||
})
|
||||
|
||||
describe('waitForHumanResponse race condition fix', () => {
|
||||
it('pending challenge is stored synchronously before promise resolves', () => {
|
||||
// Call waitForHumanResponse — this must store the pending challenge
|
||||
// SYNCHRONOUSLY (inside Promise constructor), not in a microtask
|
||||
const { promise, choices } = waitForHumanResponse('fight-1', 'bot-1', mockChallenge, 1)
|
||||
|
||||
// IMMEDIATELY after, getPendingChallenge must find it
|
||||
// (no await needed — this proves it's synchronous)
|
||||
const pending = getPendingChallenge('fight-1', 'bot-1')
|
||||
expect(pending).not.toBeNull()
|
||||
expect(pending!.prompt).toBe('What year was Bitcoin launched?')
|
||||
expect(pending!.choices).toEqual(choices)
|
||||
|
||||
// Clean up by submitting
|
||||
submitHumanResponse('fight-1', 'bot-1', '2009')
|
||||
})
|
||||
|
||||
it('choices are returned from waitForHumanResponse for SSE inclusion', () => {
|
||||
const { choices } = waitForHumanResponse('fight-2', 'bot-2', mockChallenge, 1)
|
||||
|
||||
// Choices must be non-empty and include the correct answer
|
||||
expect(choices.length).toBeGreaterThanOrEqual(2)
|
||||
expect(choices).toContain('2009')
|
||||
|
||||
submitHumanResponse('fight-2', 'bot-2', '2009')
|
||||
})
|
||||
})
|
||||
|
||||
describe('waitForHumanResponse timeout', () => {
|
||||
it('times out and resolves with null answer', async () => {
|
||||
vi.useFakeTimers()
|
||||
const shortChallenge = { ...mockChallenge, timeout_ms: 100 }
|
||||
const { promise } = waitForHumanResponse('fight-3', 'bot-3', shortChallenge, 1)
|
||||
|
||||
// Advance past timeout (100ms challenge + 5000ms human extra)
|
||||
vi.advanceTimersByTime(5200)
|
||||
const result = await promise
|
||||
expect(result.timedOut).toBe(true)
|
||||
expect(result.answer).toBeNull()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
||||
|
||||
describe('submitHumanResponse', () => {
|
||||
it('accepts submission and resolves promise', async () => {
|
||||
const { promise } = waitForHumanResponse('fight-4', 'bot-4', mockChallenge, 1)
|
||||
|
||||
const accepted = submitHumanResponse('fight-4', 'bot-4', '2009', 'Bitcoin fixes this')
|
||||
expect(accepted).toBe(true)
|
||||
|
||||
const result = await promise
|
||||
expect(result.answer).toBe('2009')
|
||||
expect(result.trashTalk).toBe('Bitcoin fixes this')
|
||||
expect(result.timedOut).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for unknown fight/bot', () => {
|
||||
const accepted = submitHumanResponse('nonexistent', 'nobody', 'answer')
|
||||
expect(accepted).toBe(false)
|
||||
})
|
||||
|
||||
it('truncates long answers to 2000 chars', async () => {
|
||||
const { promise } = waitForHumanResponse('fight-5', 'bot-5', mockChallenge, 1)
|
||||
const longAnswer = 'x'.repeat(3000)
|
||||
submitHumanResponse('fight-5', 'bot-5', longAnswer)
|
||||
const result = await promise
|
||||
expect(result.answer!.length).toBe(2000)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user