456 lines
12 KiB
TypeScript
456 lines
12 KiB
TypeScript
/**
|
|
* @vitest-environment node
|
|
*/
|
|
|
|
import { eq } from 'drizzle-orm'
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { db, schema } from '../src/db'
|
|
import { createRoom } from '../src/lib/arcade/room-manager'
|
|
import { addRoomMember } from '../src/lib/arcade/room-membership'
|
|
|
|
/**
|
|
* Arcade Rooms API E2E Tests
|
|
*
|
|
* Tests the full arcade room system:
|
|
* - Room CRUD operations
|
|
* - Member management
|
|
* - Access control
|
|
* - Room code lookups
|
|
*/
|
|
|
|
describe('Arcade Rooms API', () => {
|
|
let testUserId1: string
|
|
let testUserId2: string
|
|
let testGuestId1: string
|
|
let testGuestId2: string
|
|
let testRoomId: string
|
|
|
|
beforeEach(async () => {
|
|
// Create test users
|
|
testGuestId1 = `test-guest-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
testGuestId2 = `test-guest-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
|
|
const [user1] = await db.insert(schema.users).values({ guestId: testGuestId1 }).returning()
|
|
const [user2] = await db.insert(schema.users).values({ guestId: testGuestId2 }).returning()
|
|
|
|
testUserId1 = user1.id
|
|
testUserId2 = user2.id
|
|
})
|
|
|
|
afterEach(async () => {
|
|
// Clean up rooms (cascade deletes members)
|
|
if (testRoomId) {
|
|
await db.delete(schema.arcadeRooms).where(eq(schema.arcadeRooms.id, testRoomId))
|
|
}
|
|
|
|
// Clean up users
|
|
await db.delete(schema.users).where(eq(schema.users.id, testUserId1))
|
|
await db.delete(schema.users).where(eq(schema.users.id, testUserId2))
|
|
})
|
|
|
|
describe('Room Creation', () => {
|
|
it('creates a room with valid data', async () => {
|
|
const room = await createRoom({
|
|
name: 'Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User',
|
|
gameName: 'matching',
|
|
gameConfig: { difficulty: 6 },
|
|
})
|
|
|
|
testRoomId = room.id
|
|
|
|
expect(room).toBeDefined()
|
|
expect(room.name).toBe('Test Room')
|
|
expect(room.createdBy).toBe(testGuestId1)
|
|
expect(room.gameName).toBe('matching')
|
|
expect(room.status).toBe('lobby')
|
|
expect(room.accessMode).toBe('open')
|
|
expect(room.ttlMinutes).toBe(60)
|
|
expect(room.code).toMatch(/^[A-Z0-9]{6}$/)
|
|
})
|
|
|
|
it('creates room with custom TTL', async () => {
|
|
const room = await createRoom({
|
|
name: 'Custom TTL Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
ttlMinutes: 120,
|
|
})
|
|
|
|
testRoomId = room.id
|
|
|
|
expect(room.ttlMinutes).toBe(120)
|
|
})
|
|
|
|
it('generates unique room codes', async () => {
|
|
const room1 = await createRoom({
|
|
name: 'Room 1',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'User 1',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
|
|
const room2 = await createRoom({
|
|
name: 'Room 2',
|
|
createdBy: testGuestId2,
|
|
creatorName: 'User 2',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
|
|
// Clean up both rooms
|
|
testRoomId = room1.id
|
|
await db.delete(schema.arcadeRooms).where(eq(schema.arcadeRooms.id, room2.id))
|
|
|
|
expect(room1.code).not.toBe(room2.code)
|
|
})
|
|
})
|
|
|
|
describe('Room Retrieval', () => {
|
|
beforeEach(async () => {
|
|
// Create a test room
|
|
const room = await createRoom({
|
|
name: 'Retrieval Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
testRoomId = room.id
|
|
})
|
|
|
|
it('retrieves room by ID', async () => {
|
|
const room = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, testRoomId),
|
|
})
|
|
|
|
expect(room).toBeDefined()
|
|
expect(room?.id).toBe(testRoomId)
|
|
expect(room?.name).toBe('Retrieval Test Room')
|
|
})
|
|
|
|
it('retrieves room by code', async () => {
|
|
const createdRoom = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, testRoomId),
|
|
})
|
|
|
|
const room = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.code, createdRoom!.code),
|
|
})
|
|
|
|
expect(room).toBeDefined()
|
|
expect(room?.id).toBe(testRoomId)
|
|
})
|
|
|
|
it('returns undefined for non-existent room', async () => {
|
|
const room = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, 'nonexistent-room-id'),
|
|
})
|
|
|
|
expect(room).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('Room Updates', () => {
|
|
beforeEach(async () => {
|
|
const room = await createRoom({
|
|
name: 'Update Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
testRoomId = room.id
|
|
})
|
|
|
|
it('updates room name', async () => {
|
|
const [updated] = await db
|
|
.update(schema.arcadeRooms)
|
|
.set({ name: 'Updated Name' })
|
|
.where(eq(schema.arcadeRooms.id, testRoomId))
|
|
.returning()
|
|
|
|
expect(updated.name).toBe('Updated Name')
|
|
})
|
|
|
|
it('locks room', async () => {
|
|
const [updated] = await db
|
|
.update(schema.arcadeRooms)
|
|
.set({ accessMode: 'locked' })
|
|
.where(eq(schema.arcadeRooms.id, testRoomId))
|
|
.returning()
|
|
|
|
expect(updated.accessMode).toBe('locked')
|
|
})
|
|
|
|
it('updates room status', async () => {
|
|
const [updated] = await db
|
|
.update(schema.arcadeRooms)
|
|
.set({ status: 'playing' })
|
|
.where(eq(schema.arcadeRooms.id, testRoomId))
|
|
.returning()
|
|
|
|
expect(updated.status).toBe('playing')
|
|
})
|
|
|
|
it('updates lastActivity on any change', async () => {
|
|
const originalRoom = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, testRoomId),
|
|
})
|
|
|
|
// Wait a bit to ensure different timestamp (at least 1 second for SQLite timestamp resolution)
|
|
await new Promise((resolve) => setTimeout(resolve, 1100))
|
|
|
|
const [updated] = await db
|
|
.update(schema.arcadeRooms)
|
|
.set({ name: 'Activity Test', lastActivity: new Date() })
|
|
.where(eq(schema.arcadeRooms.id, testRoomId))
|
|
.returning()
|
|
|
|
expect(updated.lastActivity.getTime()).toBeGreaterThan(originalRoom!.lastActivity.getTime())
|
|
})
|
|
})
|
|
|
|
describe('Room Deletion', () => {
|
|
it('deletes room', async () => {
|
|
const room = await createRoom({
|
|
name: 'Delete Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
|
|
await db.delete(schema.arcadeRooms).where(eq(schema.arcadeRooms.id, room.id))
|
|
|
|
const deleted = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, room.id),
|
|
})
|
|
|
|
expect(deleted).toBeUndefined()
|
|
})
|
|
|
|
it('cascades delete to room members', async () => {
|
|
const room = await createRoom({
|
|
name: 'Cascade Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
|
|
// Add member
|
|
await addRoomMember({
|
|
roomId: room.id,
|
|
userId: testGuestId1,
|
|
displayName: 'Test User',
|
|
})
|
|
|
|
// Verify member exists
|
|
const membersBefore = await db.query.roomMembers.findMany({
|
|
where: eq(schema.roomMembers.roomId, room.id),
|
|
})
|
|
expect(membersBefore).toHaveLength(1)
|
|
|
|
// Delete room
|
|
await db.delete(schema.arcadeRooms).where(eq(schema.arcadeRooms.id, room.id))
|
|
|
|
// Verify members deleted
|
|
const membersAfter = await db.query.roomMembers.findMany({
|
|
where: eq(schema.roomMembers.roomId, room.id),
|
|
})
|
|
expect(membersAfter).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe('Room Members', () => {
|
|
beforeEach(async () => {
|
|
const room = await createRoom({
|
|
name: 'Members Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Test User 1',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
testRoomId = room.id
|
|
})
|
|
|
|
it('adds member to room', async () => {
|
|
const result = await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId1,
|
|
displayName: 'Test User 1',
|
|
isCreator: true,
|
|
})
|
|
|
|
expect(result.member).toBeDefined()
|
|
expect(result.member.roomId).toBe(testRoomId)
|
|
expect(result.member.userId).toBe(testGuestId1)
|
|
expect(result.member.displayName).toBe('Test User 1')
|
|
expect(result.member.isCreator).toBe(true)
|
|
expect(result.member.isOnline).toBe(true)
|
|
})
|
|
|
|
it('adds multiple members to room', async () => {
|
|
await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId1,
|
|
displayName: 'User 1',
|
|
})
|
|
|
|
await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId2,
|
|
displayName: 'User 2',
|
|
})
|
|
|
|
const members = await db.query.roomMembers.findMany({
|
|
where: eq(schema.roomMembers.roomId, testRoomId),
|
|
})
|
|
|
|
expect(members).toHaveLength(2)
|
|
})
|
|
|
|
it('updates existing member instead of creating duplicate', async () => {
|
|
// Add member first time
|
|
await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId1,
|
|
displayName: 'First Time',
|
|
})
|
|
|
|
// Add same member again
|
|
await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId1,
|
|
displayName: 'Second Time',
|
|
})
|
|
|
|
const members = await db.query.roomMembers.findMany({
|
|
where: eq(schema.roomMembers.roomId, testRoomId),
|
|
})
|
|
|
|
// Should still only have 1 member
|
|
expect(members).toHaveLength(1)
|
|
})
|
|
|
|
it('removes member from room', async () => {
|
|
const result = await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId1,
|
|
displayName: 'Test User',
|
|
})
|
|
|
|
await db.delete(schema.roomMembers).where(eq(schema.roomMembers.id, result.member.id))
|
|
|
|
const members = await db.query.roomMembers.findMany({
|
|
where: eq(schema.roomMembers.roomId, testRoomId),
|
|
})
|
|
|
|
expect(members).toHaveLength(0)
|
|
})
|
|
|
|
it('tracks online status', async () => {
|
|
const result = await addRoomMember({
|
|
roomId: testRoomId,
|
|
userId: testGuestId1,
|
|
displayName: 'Test User',
|
|
})
|
|
|
|
expect(result.member.isOnline).toBe(true)
|
|
|
|
// Set offline
|
|
const [updated] = await db
|
|
.update(schema.roomMembers)
|
|
.set({ isOnline: false })
|
|
.where(eq(schema.roomMembers.id, result.member.id))
|
|
.returning()
|
|
|
|
expect(updated.isOnline).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('Access Control', () => {
|
|
beforeEach(async () => {
|
|
const room = await createRoom({
|
|
name: 'Access Test Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'Creator',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
testRoomId = room.id
|
|
})
|
|
|
|
it('identifies room creator correctly', async () => {
|
|
const room = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, testRoomId),
|
|
})
|
|
|
|
expect(room?.createdBy).toBe(testGuestId1)
|
|
})
|
|
|
|
it('distinguishes creator from other users', async () => {
|
|
const room = await db.query.arcadeRooms.findFirst({
|
|
where: eq(schema.arcadeRooms.id, testRoomId),
|
|
})
|
|
|
|
expect(room?.createdBy).not.toBe(testGuestId2)
|
|
})
|
|
})
|
|
|
|
describe('Room Listing', () => {
|
|
beforeEach(async () => {
|
|
// Create multiple test rooms
|
|
const room1 = await createRoom({
|
|
name: 'Matching Room',
|
|
createdBy: testGuestId1,
|
|
creatorName: 'User 1',
|
|
gameName: 'matching',
|
|
gameConfig: {},
|
|
})
|
|
|
|
const room2 = await createRoom({
|
|
name: 'Memory Quiz Room',
|
|
createdBy: testGuestId2,
|
|
creatorName: 'User 2',
|
|
gameName: 'memory-quiz',
|
|
gameConfig: {},
|
|
})
|
|
|
|
testRoomId = room1.id
|
|
|
|
// Clean up room2 after test
|
|
afterEach(async () => {
|
|
await db.delete(schema.arcadeRooms).where(eq(schema.arcadeRooms.id, room2.id))
|
|
})
|
|
})
|
|
|
|
it('lists all active rooms', async () => {
|
|
const rooms = await db.query.arcadeRooms.findMany({
|
|
where: eq(schema.arcadeRooms.status, 'lobby'),
|
|
})
|
|
|
|
expect(rooms.length).toBeGreaterThanOrEqual(2)
|
|
})
|
|
|
|
it('excludes locked rooms from listing', async () => {
|
|
// Lock one room
|
|
await db
|
|
.update(schema.arcadeRooms)
|
|
.set({ accessMode: 'locked' })
|
|
.where(eq(schema.arcadeRooms.id, testRoomId))
|
|
|
|
const openRooms = await db.query.arcadeRooms.findMany({
|
|
where: eq(schema.arcadeRooms.accessMode, 'open'),
|
|
})
|
|
|
|
expect(openRooms.every((r) => r.accessMode === 'open')).toBe(true)
|
|
})
|
|
})
|
|
})
|