feat(games): implement RoomManager with room lifecycle and tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
syntaxbullet
2026-04-02 13:26:26 +02:00
parent 55df982a0b
commit 33a1848096
2 changed files with 309 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
import { describe, it, expect, beforeEach } from "bun:test";
import { RoomManager } from "./RoomManager";
import { gameRegistry } from "@shared/games/registry";
import { chessPlugin } from "@shared/games/chess/plugin";
// Register chess plugin for tests
if (!gameRegistry.get("chess")) {
gameRegistry.register(chessPlugin);
}
describe("RoomManager", () => {
let manager: RoomManager;
beforeEach(() => {
manager = new RoomManager();
});
describe("createRoom", () => {
it("should create a room and return its id", () => {
const result = manager.createRoom("chess", "player1");
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.roomId).toBeDefined();
expect(typeof result.roomId).toBe("string");
}
});
it("should reject unknown game type", () => {
const result = manager.createRoom("unknown-game", "player1");
expect(result.ok).toBe(false);
});
it("should add creator as first player", () => {
const result = manager.createRoom("chess", "player1");
if (result.ok) {
const room = manager.getRoom(result.roomId);
expect(room?.players).toContain("player1");
expect(room?.host).toBe("player1");
expect(room?.status).toBe("waiting");
}
});
});
describe("joinRoom", () => {
it("should add a player to a waiting room", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
const join = manager.joinRoom(create.roomId, "player2", "player");
expect(join.ok).toBe(true);
});
it("should auto-start when room reaches maxPlayers", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.joinRoom(create.roomId, "player2", "player");
const room = manager.getRoom(create.roomId);
expect(room?.status).toBe("playing");
expect(room?.state).toBeDefined();
});
it("should allow joining as spectator when game is playing", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.joinRoom(create.roomId, "player2", "player");
const spec = manager.joinRoom(create.roomId, "spectator1", "spectator");
expect(spec.ok).toBe(true);
});
it("should reject joining full room as player", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.joinRoom(create.roomId, "player2", "player");
const result = manager.joinRoom(create.roomId, "player3", "player");
expect(result.ok).toBe(false);
});
it("should reject joining nonexistent room", () => {
const result = manager.joinRoom("fake-id", "player1", "player");
expect(result.ok).toBe(false);
});
});
describe("handleAction", () => {
it("should apply a valid game action", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.joinRoom(create.roomId, "player2", "player");
const result = manager.handleAction(create.roomId, "player1", { type: "move", from: [6, 4], to: [4, 4] });
expect(result.ok).toBe(true);
});
it("should reject action from spectator", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.joinRoom(create.roomId, "player2", "player");
manager.joinRoom(create.roomId, "spectator1", "spectator");
const result = manager.handleAction(create.roomId, "spectator1", { type: "move", from: [6, 4], to: [4, 4] });
expect(result.ok).toBe(false);
});
});
describe("leaveRoom", () => {
it("should remove a player from the room", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.leaveRoom(create.roomId, "player1");
// Room is deleted when last player leaves a waiting room
const room = manager.getRoom(create.roomId);
expect(room).toBeUndefined();
});
it("should remove a spectator from the room", () => {
const create = manager.createRoom("chess", "player1");
if (!create.ok) throw new Error("Failed to create room");
manager.joinRoom(create.roomId, "player2", "player");
manager.joinRoom(create.roomId, "spec1", "spectator");
manager.leaveRoom(create.roomId, "spec1");
const room = manager.getRoom(create.roomId);
expect(room?.spectators.has("spec1")).toBe(false);
});
});
describe("listRooms", () => {
it("should return summaries of all rooms", () => {
manager.createRoom("chess", "player1");
manager.createRoom("chess", "player2");
const rooms = manager.listRooms();
expect(rooms.length).toBe(2);
expect(rooms[0].gameSlug).toBe("chess");
expect(rooms[0].status).toBe("waiting");
});
it("should filter by game type", () => {
manager.createRoom("chess", "player1");
const rooms = manager.listRooms("chess");
expect(rooms.length).toBe(1);
const empty = manager.listRooms("blackjack");
expect(empty.length).toBe(0);
});
});
});