"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const express_1 = __importDefault(require("express")); const bcryptjs_1 = __importDefault(require("bcryptjs")); const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); const supertest_1 = __importDefault(require("supertest")); const client_1 = require("@prisma/client"); const prisma = new client_1.PrismaClient(); const JWT_SECRET = 'test-secret-key'; function createApp() { const app = (0, express_1.default)(); app.use(express_1.default.json()); const authenticate = async (req, res, next) => { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'No token provided' }); } const token = authHeader.split(' ')[1]; try { const decoded = jsonwebtoken_1.default.verify(token, JWT_SECRET); const user = await prisma.user.findUnique({ where: { id: decoded.userId }, select: { id: true, email: true, name: true, isAdmin: true } }); if (!user) { return res.status(401).json({ error: 'User not found' }); } req.user = user; next(); } catch { return res.status(401).json({ error: 'Invalid token' }); } }; app.get('/game/:gameId', async (req, res) => { try { const { gameId } = req.params; const teams = await prisma.team.findMany({ where: { gameId }, include: { members: { include: { user: { select: { id: true, name: true, email: true } } } }, captain: { select: { id: true, name: true } }, teamRoutes: { include: { route: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } } }, orderBy: { createdAt: 'asc' } }); res.json(teams); } catch { res.status(500).json({ error: 'Failed to get teams' }); } }); app.post('/game/:gameId', authenticate, async (req, res) => { try { const { gameId } = req.params; const { name } = req.body; const game = await prisma.game.findUnique({ where: { id: gameId }, include: { teams: true } }); if (!game) { return res.status(404).json({ error: 'Game not found' }); } if (game.status !== 'DRAFT' && game.status !== 'LIVE') { return res.status(400).json({ error: 'Cannot join game at this time' }); } const existingMember = await prisma.teamMember.findFirst({ where: { userId: req.user.id, team: { gameId } } }); if (existingMember) { return res.status(400).json({ error: 'Already in a team for this game' }); } const team = await prisma.team.create({ data: { gameId, name, captainId: req.user.id } }); await prisma.teamMember.create({ data: { teamId: team.id, userId: req.user.id } }); const created = await prisma.team.findUnique({ where: { id: team.id }, include: { members: { include: { user: { select: { id: true, name: true, email: true } } } }, captain: { select: { id: true, name: true } }, teamRoutes: { include: { route: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } } } }); res.json(created); } catch { res.status(500).json({ error: 'Failed to create team' }); } }); app.post('/:teamId/join', authenticate, async (req, res) => { try { const { teamId } = req.params; const team = await prisma.team.findUnique({ where: { id: teamId }, include: { game: true, members: true } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } if (team.members.length >= 5) { return res.status(400).json({ error: 'Team is full (max 5 members)' }); } const existingMember = await prisma.teamMember.findFirst({ where: { userId: req.user.id, teamId } }); if (existingMember) { return res.status(400).json({ error: 'Already in this team' }); } const gameMember = await prisma.teamMember.findFirst({ where: { userId: req.user.id, team: { gameId: team.gameId } } }); if (gameMember) { return res.status(400).json({ error: 'Already in another team for this game' }); } await prisma.teamMember.create({ data: { teamId, userId: req.user.id } }); res.json({ message: 'Joined team successfully' }); } catch { res.status(500).json({ error: 'Failed to join team' }); } }); app.post('/:teamId/leave', authenticate, async (req, res) => { try { const { teamId } = req.params; const team = await prisma.team.findUnique({ where: { id: teamId } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } if (team.captainId === req.user.id) { return res.status(400).json({ error: 'Captain cannot leave the team' }); } await prisma.teamMember.deleteMany({ where: { teamId, userId: req.user.id } }); res.json({ message: 'Left team successfully' }); } catch { res.status(500).json({ error: 'Failed to leave team' }); } }); app.post('/:teamId/assign-route', authenticate, async (req, res) => { try { const { teamId } = req.params; const { routeId } = req.body; const team = await prisma.team.findUnique({ where: { id: teamId }, include: { game: true, teamRoutes: true } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } if (team.game.gameMasterId !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } const route = await prisma.route.findUnique({ where: { id: routeId } }); if (!route || route.gameId !== team.gameId) { return res.status(400).json({ error: 'Invalid route for this game' }); } await prisma.teamRoute.deleteMany({ where: { teamId } }); const teamRoute = await prisma.teamRoute.create({ data: { teamId, routeId }, include: { route: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } }); res.json(teamRoute); } catch { res.status(500).json({ error: 'Failed to assign route' }); } }); app.post('/:teamId/advance', authenticate, async (req, res) => { try { const { teamId } = req.params; const team = await prisma.team.findUnique({ where: { id: teamId }, include: { game: true, teamRoutes: { include: { route: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } } } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } if (team.game.gameMasterId !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } const teamRoute = team.teamRoutes[0]; if (!teamRoute) { return res.status(400).json({ error: 'Team has no assigned route' }); } const legs = teamRoute.route.routeLegs; const currentLegIndex = team.currentLegIndex; let nextLegIndex = currentLegIndex; if (currentLegIndex < legs.length - 1) { nextLegIndex = currentLegIndex + 1; } const updated = await prisma.team.update({ where: { id: teamId }, data: { currentLegIndex: nextLegIndex, status: nextLegIndex >= legs.length - 1 ? 'FINISHED' : 'ACTIVE' }, include: { members: { include: { user: { select: { id: true, name: true } } } }, teamRoutes: { include: { route: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } } } }); res.json(updated); } catch { res.status(500).json({ error: 'Failed to advance team' }); } }); app.post('/:teamId/deduct', authenticate, async (req, res) => { try { const { teamId } = req.params; const { seconds } = req.body; const team = await prisma.team.findUnique({ where: { id: teamId }, include: { game: true } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } if (team.game.gameMasterId !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } const deduction = seconds || 60; const updated = await prisma.team.update({ where: { id: teamId }, data: { totalTimeDeduction: { increment: deduction } } }); res.json(updated); } catch { res.status(500).json({ error: 'Failed to deduct time' }); } }); app.post('/:teamId/disqualify', authenticate, async (req, res) => { try { const { teamId } = req.params; const team = await prisma.team.findUnique({ where: { id: teamId }, include: { game: true } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } if (team.game.gameMasterId !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } const updated = await prisma.team.update({ where: { id: teamId }, data: { status: 'DISQUALIFIED' } }); res.json(updated); } catch { res.status(500).json({ error: 'Failed to disqualify team' }); } }); app.post('/:teamId/location', authenticate, async (req, res) => { try { const { teamId } = req.params; const { lat, lng } = req.body; const team = await prisma.team.findUnique({ where: { id: teamId } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } const member = await prisma.teamMember.findFirst({ where: { teamId, userId: req.user.id } }); if (!member && team.captainId !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } const updated = await prisma.team.update({ where: { id: teamId }, data: { lat, lng } }); res.json(updated); } catch { res.status(500).json({ error: 'Failed to update location' }); } }); app.get('/:teamId', authenticate, async (req, res) => { try { const { teamId } = req.params; const team = await prisma.team.findUnique({ where: { id: teamId }, include: { members: { include: { user: { select: { id: true, name: true, email: true } } } }, captain: { select: { id: true, name: true } }, game: { include: { routes: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } }, teamRoutes: { include: { route: { include: { routeLegs: { orderBy: { sequenceNumber: 'asc' } } } } } } } }); if (!team) { return res.status(404).json({ error: 'Team not found' }); } res.json(team); } catch { res.status(500).json({ error: 'Failed to get team' }); } }); return app; } (0, vitest_1.describe)('Teams API', () => { let app; let gameMasterToken; let gameMasterId; let playerToken; let playerId; let gameId; let routeId; async function cleanup() { await prisma.photoSubmission.deleteMany(); await prisma.routeLeg.deleteMany(); await prisma.teamRoute.deleteMany(); await prisma.teamMember.deleteMany(); await prisma.team.deleteMany(); await prisma.route.deleteMany(); await prisma.chatMessage.deleteMany(); await prisma.locationHistory.deleteMany(); await prisma.game.deleteMany(); await prisma.user.deleteMany(); await prisma.systemSettings.deleteMany(); await prisma.bannedEmail.deleteMany(); await prisma.apiKey.deleteMany(); } (0, vitest_1.beforeAll)(async () => { app = createApp(); await cleanup(); }); (0, vitest_1.afterAll)(async () => { await cleanup(); await prisma.$disconnect(); }); (0, vitest_1.beforeEach)(async () => { await cleanup(); const passwordHash = await bcryptjs_1.default.hash('password123', 10); const gm = await prisma.user.create({ data: { email: 'gm@test.com', passwordHash, name: 'Game Master', isAdmin: true } }); gameMasterId = gm.id; gameMasterToken = jsonwebtoken_1.default.sign({ userId: gm.id }, JWT_SECRET, { expiresIn: '7d' }); const player = await prisma.user.create({ data: { email: 'player@test.com', passwordHash, name: 'Player' } }); playerId = player.id; playerToken = jsonwebtoken_1.default.sign({ userId: player.id }, JWT_SECRET, { expiresIn: '7d' }); const game = await prisma.game.create({ data: { name: 'Test Game', gameMasterId, status: 'LIVE' } }); gameId = game.id; const route = await prisma.route.create({ data: { name: 'Test Route', gameId } }); routeId = route.id; await prisma.routeLeg.createMany({ data: [ { routeId, sequenceNumber: 1, description: 'First leg', conditionType: 'photo' }, { routeId, sequenceNumber: 2, description: 'Second leg', conditionType: 'photo' }, { routeId, sequenceNumber: 3, description: 'Final leg', conditionType: 'photo' } ] }); }); (0, vitest_1.describe)('GET /game/:gameId', () => { (0, vitest_1.it)('should list teams for a game', async () => { await prisma.team.create({ data: { name: 'Team Alpha', gameId, captainId: gameMasterId } }); const res = await (0, supertest_1.default)(app).get(`/game/${gameId}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.length).toBe(1); (0, vitest_1.expect)(res.body[0].name).toBe('Team Alpha'); }); (0, vitest_1.it)('should return empty array for game with no teams', async () => { const res = await (0, supertest_1.default)(app).get(`/game/${gameId}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body).toEqual([]); }); }); (0, vitest_1.describe)('POST /game/:gameId', () => { (0, vitest_1.it)('should create a team', async () => { const res = await (0, supertest_1.default)(app) .post(`/game/${gameId}`) .set('Authorization', `Bearer ${playerToken}`) .send({ name: 'New Team' }); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.name).toBe('New Team'); (0, vitest_1.expect)(res.body.captain.id).toBe(playerId); (0, vitest_1.expect)(res.body.members.length).toBe(1); }); (0, vitest_1.it)('should return 401 without token', async () => { const res = await (0, supertest_1.default)(app) .post(`/game/${gameId}`) .send({ name: 'New Team' }); (0, vitest_1.expect)(res.status).toBe(401); }); (0, vitest_1.it)('should return 404 for non-existent game', async () => { const res = await (0, supertest_1.default)(app) .post('/game/non-existent-id') .set('Authorization', `Bearer ${playerToken}`) .send({ name: 'New Team' }); (0, vitest_1.expect)(res.status).toBe(404); }); (0, vitest_1.it)('should not allow joining ended game', async () => { await prisma.game.update({ where: { id: gameId }, data: { status: 'ENDED' } }); const res = await (0, supertest_1.default)(app) .post(`/game/${gameId}`) .set('Authorization', `Bearer ${playerToken}`) .send({ name: 'Late Team' }); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('Cannot join game'); }); (0, vitest_1.it)('should not allow user already in a team', async () => { await prisma.team.create({ data: { name: 'First Team', gameId, captainId: playerId } }); await prisma.teamMember.create({ data: { teamId: (await prisma.team.findFirst({ where: { gameId } })).id, userId: playerId } }); const res = await (0, supertest_1.default)(app) .post(`/game/${gameId}`) .set('Authorization', `Bearer ${playerToken}`) .send({ name: 'Second Team' }); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('Already in a team'); }); }); (0, vitest_1.describe)('POST /:teamId/join', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Joinable Team', gameId, captainId: gameMasterId } }); teamId = team.id; await prisma.teamMember.create({ data: { teamId, userId: gameMasterId } }); }); (0, vitest_1.it)('should allow player to join team', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/join`) .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.message).toBe('Joined team successfully'); const members = await prisma.teamMember.findMany({ where: { teamId } }); (0, vitest_1.expect)(members.length).toBe(2); }); (0, vitest_1.it)('should return 401 without token', async () => { const res = await (0, supertest_1.default)(app).post(`/${teamId}/join`); (0, vitest_1.expect)(res.status).toBe(401); }); (0, vitest_1.it)('should return 404 for non-existent team', async () => { const res = await (0, supertest_1.default)(app) .post('/non-existent-team/join') .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(404); }); (0, vitest_1.it)('should not allow joining full team', async () => { for (let i = 0; i < 5; i++) { const user = await prisma.user.create({ data: { email: `member${i}@test.com`, passwordHash: await bcryptjs_1.default.hash('pass', 10), name: `Member ${i}` } }); await prisma.teamMember.create({ data: { teamId, userId: user.id } }); } const newPlayer = await prisma.user.create({ data: { email: 'overflow@test.com', passwordHash: await bcryptjs_1.default.hash('pass', 10), name: 'Overflow' } }); const newToken = jsonwebtoken_1.default.sign({ userId: newPlayer.id }, JWT_SECRET); const res = await (0, supertest_1.default)(app) .post(`/${teamId}/join`) .set('Authorization', `Bearer ${newToken}`); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('Team is full'); }); (0, vitest_1.it)('should not allow joining same team twice', async () => { await (0, supertest_1.default)(app) .post(`/${teamId}/join`) .set('Authorization', `Bearer ${playerToken}`); const res = await (0, supertest_1.default)(app) .post(`/${teamId}/join`) .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('Already in this team'); }); }); (0, vitest_1.describe)('POST /:teamId/leave', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Leavable Team', gameId, captainId: gameMasterId } }); teamId = team.id; await prisma.teamMember.create({ data: { teamId, userId: gameMasterId } }); await prisma.teamMember.create({ data: { teamId, userId: playerId } }); }); (0, vitest_1.it)('should allow player to leave team', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/leave`) .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.message).toBe('Left team successfully'); const members = await prisma.teamMember.findMany({ where: { teamId } }); (0, vitest_1.expect)(members.length).toBe(1); }); (0, vitest_1.it)('should return 401 without token', async () => { const res = await (0, supertest_1.default)(app).post(`/${teamId}/leave`); (0, vitest_1.expect)(res.status).toBe(401); }); (0, vitest_1.it)('should not allow captain to leave', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/leave`) .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('Captain cannot leave'); }); }); (0, vitest_1.describe)('POST /:teamId/assign-route', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Route Team', gameId, captainId: gameMasterId } }); teamId = team.id; }); (0, vitest_1.it)('should assign route to team', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/assign-route`) .set('Authorization', `Bearer ${gameMasterToken}`) .send({ routeId }); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.route.id).toBe(routeId); }); (0, vitest_1.it)('should return 403 for non-game-master', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/assign-route`) .set('Authorization', `Bearer ${playerToken}`) .send({ routeId }); (0, vitest_1.expect)(res.status).toBe(403); }); (0, vitest_1.it)('should return 404 for non-existent team', async () => { const res = await (0, supertest_1.default)(app) .post('/non-existent/assign-route') .set('Authorization', `Bearer ${gameMasterToken}`) .send({ routeId }); (0, vitest_1.expect)(res.status).toBe(404); }); (0, vitest_1.it)('should return 400 for route from different game', async () => { const otherGame = await prisma.game.create({ data: { name: 'Other Game', gameMasterId } }); const otherRoute = await prisma.route.create({ data: { name: 'Other Route', gameId: otherGame.id } }); const res = await (0, supertest_1.default)(app) .post(`/${teamId}/assign-route`) .set('Authorization', `Bearer ${gameMasterToken}`) .send({ routeId: otherRoute.id }); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('Invalid route'); }); }); (0, vitest_1.describe)('POST /:teamId/advance', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Advancing Team', gameId, captainId: gameMasterId, status: 'ACTIVE', currentLegIndex: 0 } }); teamId = team.id; await prisma.teamRoute.create({ data: { teamId, routeId } }); }); (0, vitest_1.it)('should advance team to next leg', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/advance`) .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.currentLegIndex).toBe(1); const updated = await prisma.team.findUnique({ where: { id: teamId } }); (0, vitest_1.expect)(updated.currentLegIndex).toBe(1); }); (0, vitest_1.it)('should mark team as finished on last leg', async () => { await prisma.team.update({ where: { id: teamId }, data: { currentLegIndex: 2 } }); const res = await (0, supertest_1.default)(app) .post(`/${teamId}/advance`) .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.status).toBe('FINISHED'); }); (0, vitest_1.it)('should return 400 for team without route', async () => { await prisma.teamRoute.deleteMany({ where: { teamId } }); const res = await (0, supertest_1.default)(app) .post(`/${teamId}/advance`) .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(400); (0, vitest_1.expect)(res.body.error).toContain('no assigned route'); }); (0, vitest_1.it)('should return 403 for non-game-master', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/advance`) .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(403); }); }); (0, vitest_1.describe)('POST /:teamId/deduct', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Time Team', gameId, captainId: gameMasterId, totalTimeDeduction: 0 } }); teamId = team.id; }); (0, vitest_1.it)('should deduct time from team', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/deduct`) .set('Authorization', `Bearer ${gameMasterToken}`) .send({ seconds: 120 }); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.totalTimeDeduction).toBe(120); }); (0, vitest_1.it)('should use default deduction of 60 seconds', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/deduct`) .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.totalTimeDeduction).toBe(60); }); (0, vitest_1.it)('should return 403 for non-game-master', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/deduct`) .set('Authorization', `Bearer ${playerToken}`) .send({ seconds: 30 }); (0, vitest_1.expect)(res.status).toBe(403); }); }); (0, vitest_1.describe)('POST /:teamId/disqualify', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'DQ Team', gameId, captainId: gameMasterId, status: 'ACTIVE' } }); teamId = team.id; }); (0, vitest_1.it)('should disqualify team', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/disqualify`) .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.status).toBe('DISQUALIFIED'); }); (0, vitest_1.it)('should return 403 for non-game-master', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/disqualify`) .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(403); }); (0, vitest_1.it)('should return 404 for non-existent team', async () => { const res = await (0, supertest_1.default)(app) .post('/non-existent/disqualify') .set('Authorization', `Bearer ${gameMasterToken}`); (0, vitest_1.expect)(res.status).toBe(404); }); }); (0, vitest_1.describe)('POST /:teamId/location', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Location Team', gameId, captainId: gameMasterId } }); teamId = team.id; await prisma.teamMember.create({ data: { teamId, userId: playerId } }); }); (0, vitest_1.it)('should update team location for member', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/location`) .set('Authorization', `Bearer ${playerToken}`) .send({ lat: 40.7128, lng: -74.0060 }); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.lat).toBe(40.7128); (0, vitest_1.expect)(res.body.lng).toBe(-74.0060); }); (0, vitest_1.it)('should update team location for captain', async () => { const res = await (0, supertest_1.default)(app) .post(`/${teamId}/location`) .set('Authorization', `Bearer ${gameMasterToken}`) .send({ lat: 51.5074, lng: -0.1278 }); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.lat).toBe(51.5074); }); (0, vitest_1.it)('should return 403 for non-member non-captain', async () => { const outsider = await prisma.user.create({ data: { email: 'outsider@test.com', passwordHash: await bcryptjs_1.default.hash('pass', 10), name: 'Outsider' } }); const outsiderToken = jsonwebtoken_1.default.sign({ userId: outsider.id }, JWT_SECRET); const res = await (0, supertest_1.default)(app) .post(`/${teamId}/location`) .set('Authorization', `Bearer ${outsiderToken}`) .send({ lat: 0, lng: 0 }); (0, vitest_1.expect)(res.status).toBe(403); }); }); (0, vitest_1.describe)('GET /:teamId', () => { let teamId; (0, vitest_1.beforeEach)(async () => { const team = await prisma.team.create({ data: { name: 'Get Team', gameId, captainId: gameMasterId } }); teamId = team.id; }); (0, vitest_1.it)('should get team details', async () => { const res = await (0, supertest_1.default)(app) .get(`/${teamId}`) .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(200); (0, vitest_1.expect)(res.body.name).toBe('Get Team'); (0, vitest_1.expect)(res.body.captain.id).toBe(gameMasterId); }); (0, vitest_1.it)('should return 401 without token', async () => { const res = await (0, supertest_1.default)(app).get(`/${teamId}`); (0, vitest_1.expect)(res.status).toBe(401); }); (0, vitest_1.it)('should return 404 for non-existent team', async () => { const res = await (0, supertest_1.default)(app) .get('/non-existent') .set('Authorization', `Bearer ${playerToken}`); (0, vitest_1.expect)(res.status).toBe(404); }); }); });