835 lines
36 KiB
JavaScript
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);
|
|
});
|
|
});
|
|
});
|