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 }) })