test: verify human challenge race condition fix ordering (BUG-S2)

waitForHumanResponse synchronously stores pending challenge before
returning, ensuring SSE emit happens after storage. Tests verify
ordering, choice consistency, promise resolution, and cleanup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-12 22:36:04 +00:00
co-authored by Claude Opus 4.6
parent 8468c89352
commit e48a984d96
@@ -0,0 +1,80 @@
import { describe, it, expect } from 'vitest'
import { waitForHumanResponse, getPendingChallenge, submitHumanResponse } from './human-responses.js'
import type { Challenge } from './challenges.js'
const testChallenge: Challenge = {
type: 'bitcoin_trivia',
label: 'Bitcoin Trivia',
prompt: 'What year was Bitcoin launched?',
answers: ['2009'],
scoring: 'factual',
timeout_ms: 8000,
difficulty: 'easy',
choices: ['2009', '2010', '2008'],
}
describe('waitForHumanResponse ordering', () => {
it('pending challenge is stored synchronously before promise is awaited', () => {
const fightId = `fight-ordering-${Date.now()}`
const botId = 'bot-ordering-test'
// Call waitForHumanResponse — the pending entry must be stored synchronously
const { promise, choices } = waitForHumanResponse(fightId, botId, testChallenge, 1)
// IMMEDIATELY check that getPendingChallenge returns data (before awaiting)
const pending = getPendingChallenge(fightId, botId)
expect(pending).not.toBeNull()
expect(pending!.prompt).toBe('What year was Bitcoin launched?')
expect(pending!.roundNumber).toBe(1)
expect(choices).toBeInstanceOf(Array)
expect(choices.length).toBe(3)
// Clean up: submit answer to resolve the promise
submitHumanResponse(fightId, botId, '2009')
// Consume promise to prevent unhandled rejection
return promise
})
it('choices returned by waitForHumanResponse match pending challenge choices', () => {
const fightId = `fight-choices-${Date.now()}`
const botId = 'bot-choices-test'
const { promise, choices } = waitForHumanResponse(fightId, botId, testChallenge, 2)
const pending = getPendingChallenge(fightId, botId)
expect(pending).not.toBeNull()
expect(pending!.choices).toEqual(choices)
submitHumanResponse(fightId, botId, '2009')
return promise
})
it('submitHumanResponse resolves the waiting promise', async () => {
const fightId = `fight-submit-${Date.now()}`
const botId = 'bot-submit-test'
const { promise } = waitForHumanResponse(fightId, botId, testChallenge, 3)
// Submit after a microtask delay to simulate browser response
setTimeout(() => submitHumanResponse(fightId, botId, '2009', 'nice!'), 10)
const result = await promise
expect(result.answer).toBe('2009')
expect(result.trashTalk).toBe('nice!')
expect(result.timedOut).toBe(false)
})
it('pending is cleared after submit', () => {
const fightId = `fight-clear-${Date.now()}`
const botId = 'bot-clear-test'
const { promise } = waitForHumanResponse(fightId, botId, testChallenge, 4)
submitHumanResponse(fightId, botId, '2009')
const pending = getPendingChallenge(fightId, botId)
expect(pending).toBeNull()
return promise
})
})