Files
esc/packages/server/tests/room-manager.test.ts

176 lines
5.8 KiB
TypeScript

import { describe, expect, it, beforeEach } from "vitest"
import { RoomManager } from "../src/rooms/room-manager"
import type { Act } from "@celebrate-esc/shared"
describe("RoomManager", () => {
let manager: RoomManager
beforeEach(() => {
manager = new RoomManager()
})
describe("createRoom", () => {
it("returns a 4-character room code and session ID", () => {
const result = manager.createRoom("Host")
expect(result.code).toMatch(/^[A-Z0-9]{4}$/)
expect(result.sessionId).toBeDefined()
expect(result.sessionId.length).toBe(36) // UUID
})
it("creates the host as a player in the room", () => {
const { code, sessionId } = manager.createRoom("Host")
const room = manager.getRoom(code)
expect(room).toBeDefined()
expect(room!.players).toHaveLength(1)
expect(room!.players[0]!.displayName).toBe("Host")
expect(room!.players[0]!.isHost).toBe(true)
expect(room!.players[0]!.sessionId).toBe(sessionId)
})
it("starts in lobby state", () => {
const { code } = manager.createRoom("Host")
const room = manager.getRoom(code)
expect(room!.currentAct).toBe("lobby")
})
it("generates unique room codes", () => {
const codes = new Set<string>()
for (let i = 0; i < 50; i++) {
const { code } = manager.createRoom(`Host ${i}`)
codes.add(code)
}
expect(codes.size).toBe(50)
})
})
describe("joinRoom", () => {
it("adds a player to an existing room", () => {
const { code } = manager.createRoom("Host")
const result = manager.joinRoom(code, "Player 1")
expect("sessionId" in result).toBe(true)
if ("sessionId" in result) {
const room = manager.getRoom(code)
expect(room!.players).toHaveLength(2)
expect(room!.players[1]!.displayName).toBe("Player 1")
expect(room!.players[1]!.isHost).toBe(false)
}
})
it("rejects join if room not found", () => {
const result = manager.joinRoom("ZZZZ", "Player")
expect(result).toEqual({ error: "Room not found" })
})
it("rejects join if room has ended", () => {
const { code } = manager.createRoom("Host")
// Force room to ended state
manager.advanceAct(code, manager.getRoom(code)!.hostSessionId)
manager.advanceAct(code, manager.getRoom(code)!.hostSessionId)
manager.advanceAct(code, manager.getRoom(code)!.hostSessionId)
manager.advanceAct(code, manager.getRoom(code)!.hostSessionId)
const result = manager.joinRoom(code, "Late Player")
expect(result).toEqual({ error: "Room has ended" })
})
it("rejects join if display name is taken", () => {
const { code } = manager.createRoom("Host")
manager.joinRoom(code, "Player 1")
const result = manager.joinRoom(code, "Player 1")
expect(result).toEqual({ error: "Name already taken" })
})
it("rejects join if room is full (10 players)", () => {
const { code } = manager.createRoom("Host")
for (let i = 1; i <= 9; i++) {
manager.joinRoom(code, `Player ${i}`)
}
const result = manager.joinRoom(code, "Player 10")
expect(result).toEqual({ error: "Room is full" })
})
})
describe("advanceAct", () => {
it("advances through acts in order", () => {
const { code } = manager.createRoom("Host")
const room = manager.getRoom(code)!
const hostSession = room.hostSessionId
const expectedSequence: Act[] = ["pre-show", "live-event", "scoring", "ended"]
for (const expected of expectedSequence) {
const result = manager.advanceAct(code, hostSession)
expect(result).toEqual({ newAct: expected })
expect(manager.getRoom(code)!.currentAct).toBe(expected)
}
})
it("cannot advance past ended", () => {
const { code } = manager.createRoom("Host")
const room = manager.getRoom(code)!
// Advance to ended
for (let i = 0; i < 4; i++) {
manager.advanceAct(code, room.hostSessionId)
}
const result = manager.advanceAct(code, room.hostSessionId)
expect(result).toEqual({ error: "Room has already ended" })
})
it("rejects advance from non-host", () => {
const { code } = manager.createRoom("Host")
const joinResult = manager.joinRoom(code, "Player")
if ("sessionId" in joinResult) {
const result = manager.advanceAct(code, joinResult.sessionId)
expect(result).toEqual({ error: "Only the host can advance acts" })
}
})
})
describe("endRoom", () => {
it("sets room to ended state", () => {
const { code } = manager.createRoom("Host")
const room = manager.getRoom(code)!
const result = manager.endRoom(code, room.hostSessionId)
expect(result).toEqual({ success: true })
expect(manager.getRoom(code)!.currentAct).toBe("ended")
})
it("rejects end from non-host", () => {
const { code } = manager.createRoom("Host")
const joinResult = manager.joinRoom(code, "Player")
if ("sessionId" in joinResult) {
const result = manager.endRoom(code, joinResult.sessionId)
expect(result).toEqual({ error: "Only the host can end the room" })
}
})
})
describe("getRoom", () => {
it("returns null for non-existent room", () => {
expect(manager.getRoom("ZZZZ")).toBeNull()
})
it("returns serialized room state", () => {
const { code } = manager.createRoom("Host")
const room = manager.getRoom(code)
expect(room).toBeDefined()
expect(room!.code).toBe(code)
expect(room!.currentAct).toBe("lobby")
expect(Array.isArray(room!.players)).toBe(true)
})
})
describe("reconnect", () => {
it("re-identifies an existing player by session ID", () => {
const { code, sessionId } = manager.createRoom("Host")
const result = manager.reconnectPlayer(code, sessionId)
expect(result).toEqual({ success: true, playerId: expect.any(String) })
})
it("rejects reconnect with unknown session ID", () => {
const { code } = manager.createRoom("Host")
const result = manager.reconnectPlayer(code, "00000000-0000-0000-0000-000000000000")
expect(result).toEqual({ error: "Session not found in this room" })
})
})
})