TreasureTrails/backend/dist/routes/teams.test.js

835 lines
36 KiB
JavaScript

"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);
});
});
});