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:
141
api/src/games/RoomManager.test.ts
Normal file
141
api/src/games/RoomManager.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user