test: add queue test suite with 8 cases (cooldown, join, leave, snapshot)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
21f9650585
commit
6144fa7910
@@ -0,0 +1,140 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
|
|
||||||
|
// Mock orchestrator
|
||||||
|
vi.mock('./orchestrator.js', () => ({
|
||||||
|
runFightAsync: vi.fn().mockResolvedValue('fight-123'),
|
||||||
|
isInFight: vi.fn().mockReturnValue(false),
|
||||||
|
getActiveFightId: vi.fn().mockReturnValue(null),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock mock.ts
|
||||||
|
vi.mock('./mock.js', () => ({
|
||||||
|
seedMockBots: vi.fn().mockResolvedValue(undefined),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Mock db
|
||||||
|
const mockBotRows = vi.fn()
|
||||||
|
vi.mock('../db/index.js', () => ({
|
||||||
|
db: {
|
||||||
|
select: () => ({
|
||||||
|
from: () => ({
|
||||||
|
where: () => ({
|
||||||
|
limit: () => mockBotRows(),
|
||||||
|
}),
|
||||||
|
// For matchAgainstMock's full select (no where clause on this chain)
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
bots: {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
secretHash: 'secretHash',
|
||||||
|
webhookUrl: 'webhookUrl',
|
||||||
|
eloRating: 'eloRating',
|
||||||
|
isActive: 'isActive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { joinQueue, leaveQueue, getQueueSize, getQueueSnapshot, setCooldown } = await import('./queue.js')
|
||||||
|
const { isInFight } = await import('./orchestrator.js')
|
||||||
|
|
||||||
|
const mockBot = (id: string, name = 'TestBot', elo = 1200) => [{
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
webhookUrl: 'https://example.com/webhook',
|
||||||
|
eloRating: elo,
|
||||||
|
isActive: true,
|
||||||
|
secretHash: 'abc',
|
||||||
|
ownerPubkey: 'pub123',
|
||||||
|
wins: 0,
|
||||||
|
losses: 0,
|
||||||
|
draws: 0,
|
||||||
|
winStreak: 0,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
}]
|
||||||
|
|
||||||
|
describe('queue', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
// Drain queue
|
||||||
|
while (getQueueSize() > 0) {
|
||||||
|
const snap = getQueueSnapshot()
|
||||||
|
if (snap[0]) leaveQueue(snap[0].botId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getQueueSize returns 0 when empty', () => {
|
||||||
|
expect(getQueueSize()).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('setCooldown prevents joining during cooldown', async () => {
|
||||||
|
mockBotRows.mockResolvedValue(mockBot('bot-cool'))
|
||||||
|
setCooldown('bot-cool')
|
||||||
|
|
||||||
|
await expect(joinQueue('bot-cool')).rejects.toThrow('Cooldown active')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prevents bot already in fight from joining', async () => {
|
||||||
|
mockBotRows.mockResolvedValue(mockBot('bot-fighting'))
|
||||||
|
vi.mocked(isInFight).mockReturnValueOnce(true)
|
||||||
|
|
||||||
|
await expect(joinQueue('bot-fighting')).rejects.toThrow('already in a fight')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws for non-existent bot', async () => {
|
||||||
|
mockBotRows.mockResolvedValue([])
|
||||||
|
|
||||||
|
await expect(joinQueue('nonexistent')).rejects.toThrow('Bot not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws for deactivated bot', async () => {
|
||||||
|
const bot = mockBot('bot-inactive')
|
||||||
|
bot[0].isActive = false
|
||||||
|
mockBotRows.mockResolvedValue(bot)
|
||||||
|
|
||||||
|
await expect(joinQueue('bot-inactive')).rejects.toThrow('deactivated')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaveQueue returns false for unknown bot', () => {
|
||||||
|
expect(leaveQueue('not-in-queue')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getQueueSnapshot returns queued bot info', async () => {
|
||||||
|
mockBotRows.mockResolvedValue(mockBot('bot-snap', 'SnapBot', 1400))
|
||||||
|
|
||||||
|
// joinQueue will wait in queue since no opponent — don't await
|
||||||
|
const promise = joinQueue('bot-snap')
|
||||||
|
|
||||||
|
// Flush microtasks so the async DB query resolves and bot enters queue
|
||||||
|
await new Promise(r => setTimeout(r, 0))
|
||||||
|
|
||||||
|
const snap = getQueueSnapshot()
|
||||||
|
expect(snap.length).toBe(1)
|
||||||
|
expect(snap[0].botId).toBe('bot-snap')
|
||||||
|
expect(snap[0].botName).toBe('SnapBot')
|
||||||
|
expect(snap[0].eloRating).toBe(1400)
|
||||||
|
|
||||||
|
// Cleanup: leave queue
|
||||||
|
leaveQueue('bot-snap')
|
||||||
|
await promise.catch(() => {}) // swallow 'Left queue' rejection
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaveQueue removes bot and returns true', async () => {
|
||||||
|
mockBotRows.mockResolvedValue(mockBot('bot-leave'))
|
||||||
|
|
||||||
|
const promise = joinQueue('bot-leave')
|
||||||
|
|
||||||
|
// Flush microtasks so the async DB query resolves and bot enters queue
|
||||||
|
await new Promise(r => setTimeout(r, 0))
|
||||||
|
|
||||||
|
expect(getQueueSize()).toBe(1)
|
||||||
|
|
||||||
|
const left = leaveQueue('bot-leave')
|
||||||
|
expect(left).toBe(true)
|
||||||
|
expect(getQueueSize()).toBe(0)
|
||||||
|
|
||||||
|
await promise.catch(() => {}) // swallow rejection
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user