test: add comprehensive test suite for Events API
- Created events.test.js with 34 tests covering all 10 endpoints: * GET /api/events - list events with join status * GET /api/events/:slug - event details * GET /api/events/:slug/messages - event chat messages * POST /api/events/checkin/:token - QR code checkin * GET /api/events/:slug/details - detailed event info * DELETE /api/events/:slug/leave - leave event * POST /api/events/:slug/heats - add/update user heats * GET /api/events/:slug/heats/me - get user's heats * GET /api/events/:slug/heats/all - get all heats * DELETE /api/events/:slug/heats/:id - delete heat - All 34 tests passing (100%) - events.js coverage: 82.02% (up from 8.98%) - Branch coverage: 75% - Function coverage: 88.23%
This commit is contained in:
698
backend/src/__tests__/events.test.js
Normal file
698
backend/src/__tests__/events.test.js
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
const request = require('supertest');
|
||||||
|
const app = require('../app');
|
||||||
|
const { prisma } = require('../utils/db');
|
||||||
|
const { hashPassword, generateToken } = require('../utils/auth');
|
||||||
|
|
||||||
|
// Test data
|
||||||
|
let testUser1, testUser2, testUser3;
|
||||||
|
let testToken1, testToken2, testToken3;
|
||||||
|
let testEvent, testEvent2;
|
||||||
|
let testDivision, testCompetitionType;
|
||||||
|
let checkinToken;
|
||||||
|
|
||||||
|
// Setup test data
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Clean up
|
||||||
|
await prisma.eventUserHeat.deleteMany({});
|
||||||
|
await prisma.rating.deleteMany({});
|
||||||
|
await prisma.message.deleteMany({});
|
||||||
|
await prisma.match.deleteMany({});
|
||||||
|
await prisma.chatRoom.deleteMany({});
|
||||||
|
await prisma.eventCheckinToken.deleteMany({});
|
||||||
|
await prisma.eventParticipant.deleteMany({});
|
||||||
|
await prisma.event.deleteMany({});
|
||||||
|
await prisma.user.deleteMany({});
|
||||||
|
|
||||||
|
// Create test users
|
||||||
|
testUser1 = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username: 'john_dancer',
|
||||||
|
email: 'john@example.com',
|
||||||
|
passwordHash: await hashPassword('password123'),
|
||||||
|
emailVerified: true,
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testUser2 = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username: 'sarah_swings',
|
||||||
|
email: 'sarah@example.com',
|
||||||
|
passwordHash: await hashPassword('password123'),
|
||||||
|
emailVerified: true,
|
||||||
|
firstName: 'Sarah',
|
||||||
|
lastName: 'Smith',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testUser3 = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
username: 'mike_blues',
|
||||||
|
email: 'mike@example.com',
|
||||||
|
passwordHash: await hashPassword('password123'),
|
||||||
|
emailVerified: true,
|
||||||
|
firstName: 'Mike',
|
||||||
|
lastName: 'Johnson',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate tokens
|
||||||
|
testToken1 = generateToken({ userId: testUser1.id });
|
||||||
|
testToken2 = generateToken({ userId: testUser2.id });
|
||||||
|
testToken3 = generateToken({ userId: testUser3.id });
|
||||||
|
|
||||||
|
// Get or create division and competition type
|
||||||
|
testDivision = await prisma.division.findFirst();
|
||||||
|
testCompetitionType = await prisma.competitionType.findFirst();
|
||||||
|
|
||||||
|
// If they don't exist, create them
|
||||||
|
if (!testDivision) {
|
||||||
|
testDivision = await prisma.division.create({
|
||||||
|
data: {
|
||||||
|
name: 'Intermediate',
|
||||||
|
abbreviation: 'INT',
|
||||||
|
displayOrder: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testCompetitionType) {
|
||||||
|
testCompetitionType = await prisma.competitionType.create({
|
||||||
|
data: {
|
||||||
|
name: 'Jack & Jill',
|
||||||
|
abbreviation: 'J&J',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test events
|
||||||
|
testEvent = await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
name: 'Test Dance Festival 2025',
|
||||||
|
slug: 'test-dance-festival-2025',
|
||||||
|
location: 'Test City',
|
||||||
|
startDate: new Date('2025-06-01'),
|
||||||
|
endDate: new Date('2025-06-03'),
|
||||||
|
description: 'Test event description',
|
||||||
|
worldsdcId: 'test-2025',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testEvent2 = await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
name: 'Another Dance Event',
|
||||||
|
slug: 'another-dance-event',
|
||||||
|
location: 'Another City',
|
||||||
|
startDate: new Date('2025-07-15'),
|
||||||
|
endDate: new Date('2025-07-17'),
|
||||||
|
description: 'Another test event',
|
||||||
|
worldsdcId: 'another-2025',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create checkin token for testEvent
|
||||||
|
checkinToken = await prisma.eventCheckinToken.create({
|
||||||
|
data: {
|
||||||
|
eventId: testEvent.id,
|
||||||
|
token: 'test-checkin-token-123',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add testUser1 as participant to testEvent
|
||||||
|
await prisma.eventParticipant.create({
|
||||||
|
data: {
|
||||||
|
userId: testUser1.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create chat room for testEvent
|
||||||
|
await prisma.chatRoom.create({
|
||||||
|
data: {
|
||||||
|
eventId: testEvent.id,
|
||||||
|
type: 'event',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
// Clean up
|
||||||
|
await prisma.eventUserHeat.deleteMany({});
|
||||||
|
await prisma.rating.deleteMany({});
|
||||||
|
await prisma.message.deleteMany({});
|
||||||
|
await prisma.match.deleteMany({});
|
||||||
|
await prisma.chatRoom.deleteMany({});
|
||||||
|
await prisma.eventCheckinToken.deleteMany({});
|
||||||
|
await prisma.eventParticipant.deleteMany({});
|
||||||
|
await prisma.event.deleteMany({});
|
||||||
|
await prisma.user.deleteMany({});
|
||||||
|
await prisma.$disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Events API Tests', () => {
|
||||||
|
describe('GET /api/events', () => {
|
||||||
|
it('should list all events for authenticated user', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/events')
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body).toHaveProperty('count');
|
||||||
|
expect(response.body.data).toBeInstanceOf(Array);
|
||||||
|
expect(response.body.data.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
const event = response.body.data[0];
|
||||||
|
expect(event).toHaveProperty('slug');
|
||||||
|
expect(event).toHaveProperty('name');
|
||||||
|
expect(event).toHaveProperty('location');
|
||||||
|
expect(event).toHaveProperty('startDate');
|
||||||
|
expect(event).toHaveProperty('isJoined');
|
||||||
|
expect(event).toHaveProperty('participantsCount');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show joined events first', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/events')
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
// testUser1 is participant of testEvent, so it should be first
|
||||||
|
const joinedEvents = response.body.data.filter(e => e.isJoined);
|
||||||
|
expect(joinedEvents.length).toBeGreaterThan(0);
|
||||||
|
expect(response.body.data[0].isJoined).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject request without authentication', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/events')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/events/:slug', () => {
|
||||||
|
it('should get event details by slug', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toHaveProperty('slug', testEvent.slug);
|
||||||
|
expect(response.body.data).toHaveProperty('name', testEvent.name);
|
||||||
|
expect(response.body.data).toHaveProperty('location');
|
||||||
|
expect(response.body.data).toHaveProperty('startDate');
|
||||||
|
expect(response.body.data).toHaveProperty('endDate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 404 for non-existent event', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/events/non-existent-slug')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work without authentication', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /api/events/checkin/:token', () => {
|
||||||
|
it('should check-in user to event successfully', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/checkin/${checkinToken.token}`)
|
||||||
|
.set('Authorization', `Bearer ${testToken2}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toHaveProperty('event');
|
||||||
|
expect(response.body.data.event.slug).toBe(testEvent.slug);
|
||||||
|
|
||||||
|
// Verify user is now a participant
|
||||||
|
const participant = await prisma.eventParticipant.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_eventId: {
|
||||||
|
userId: testUser2.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(participant).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle already checked-in user', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/checkin/${checkinToken.token}`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`) // testUser1 is already participant
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body).toHaveProperty('alreadyCheckedIn', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid checkin token', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/api/events/checkin/invalid-token-xyz')
|
||||||
|
.set('Authorization', `Bearer ${testToken3}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('Invalid check-in token');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject checkin without authentication', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/checkin/${checkinToken.token}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/events/:slug/details', () => {
|
||||||
|
it('should get detailed event information for participant', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/details`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toHaveProperty('event');
|
||||||
|
expect(response.body.data.event.slug).toBe(testEvent.slug);
|
||||||
|
expect(response.body.data).toHaveProperty('participants');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 404 for non-existent event', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/events/non-existent/details')
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for any authenticated user (no participant check)', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/details`)
|
||||||
|
.set('Authorization', `Bearer ${testToken3}`) // testUser3 is not participant
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toHaveProperty('event');
|
||||||
|
expect(response.body.data).toHaveProperty('participants');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /api/events/:slug/leave', () => {
|
||||||
|
it('should allow participant to leave event', async () => {
|
||||||
|
// testUser1 is already a participant from setup, use them instead
|
||||||
|
const response = await request(app)
|
||||||
|
.delete(`/api/events/${testEvent.slug}/leave`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body).toHaveProperty('message');
|
||||||
|
|
||||||
|
// Verify user is no longer a participant
|
||||||
|
const participant = await prisma.eventParticipant.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_eventId: {
|
||||||
|
userId: testUser1.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(participant).toBeNull();
|
||||||
|
|
||||||
|
// Re-add testUser1 for subsequent tests
|
||||||
|
await prisma.eventParticipant.create({
|
||||||
|
data: {
|
||||||
|
userId: testUser1.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 404 for non-existent event', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.delete('/api/events/non-existent/leave')
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 400 if user is not a participant', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.delete(`/api/events/${testEvent2.slug}/leave`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/events/:slug/messages', () => {
|
||||||
|
it('should get event chat messages (empty initially)', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/messages`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toBeInstanceOf(Array);
|
||||||
|
expect(response.body).toHaveProperty('hasMore');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 404 for non-existent event', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/events/non-existent/messages')
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject without authentication', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/messages`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('POST /api/events/:slug/heats', () => {
|
||||||
|
it('should add heats for participant', async () => {
|
||||||
|
const heats = [
|
||||||
|
{
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
competitionTypeId: testCompetitionType.id,
|
||||||
|
heatNumber: 1,
|
||||||
|
role: 'Leader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
competitionTypeId: testCompetitionType.id,
|
||||||
|
heatNumber: 2,
|
||||||
|
role: 'Follower',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/${testEvent.slug}/heats`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.send({ heats })
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toBeInstanceOf(Array);
|
||||||
|
expect(response.body.data.length).toBe(2);
|
||||||
|
expect(response.body.data[0]).toHaveProperty('heatNumber');
|
||||||
|
expect(response.body.data[0]).toHaveProperty('role');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid heats array', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/${testEvent.slug}/heats`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.send({ heats: [] })
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('non-empty array');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject heats with missing fields', async () => {
|
||||||
|
const heats = [
|
||||||
|
{
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
// missing competitionTypeId and heatNumber
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/${testEvent.slug}/heats`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.send({ heats })
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid heat number', async () => {
|
||||||
|
const heats = [
|
||||||
|
{
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
competitionTypeId: testCompetitionType.id,
|
||||||
|
heatNumber: 10, // Invalid (must be 1-9)
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/${testEvent.slug}/heats`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.send({ heats })
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('between 1 and 9');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid role', async () => {
|
||||||
|
const heats = [
|
||||||
|
{
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
competitionTypeId: testCompetitionType.id,
|
||||||
|
heatNumber: 1,
|
||||||
|
role: 'InvalidRole',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/${testEvent.slug}/heats`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.send({ heats })
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('Leader or Follower');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 403 for non-participant', async () => {
|
||||||
|
const heats = [
|
||||||
|
{
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
competitionTypeId: testCompetitionType.id,
|
||||||
|
heatNumber: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post(`/api/events/${testEvent.slug}/heats`)
|
||||||
|
.set('Authorization', `Bearer ${testToken3}`)
|
||||||
|
.send({ heats })
|
||||||
|
.expect(403);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('must be a participant');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/events/:slug/heats/me', () => {
|
||||||
|
it('should get user\'s heats for event', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/heats/me`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toBeInstanceOf(Array);
|
||||||
|
// testUser1 has heats from previous test
|
||||||
|
expect(response.body.data.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty array for user without heats', async () => {
|
||||||
|
// Add testUser3 as participant first
|
||||||
|
await prisma.eventParticipant.create({
|
||||||
|
data: {
|
||||||
|
userId: testUser3.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/heats/me`)
|
||||||
|
.set('Authorization', `Bearer ${testToken3}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).toBe(true);
|
||||||
|
expect(response.body.data).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for any authenticated user (no participant check)', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent2.slug}/heats/me`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
// testUser1 has no heats for testEvent2, so data should be empty
|
||||||
|
expect(response.body.data).toEqual([]);
|
||||||
|
expect(response.body.count).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/events/:slug/heats/all', () => {
|
||||||
|
it('should get all heats for event', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/heats/all`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body.data).toBeInstanceOf(Array);
|
||||||
|
expect(response.body.data.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
if (response.body.data.length > 0) {
|
||||||
|
const userHeats = response.body.data[0];
|
||||||
|
// Response is grouped by user
|
||||||
|
expect(userHeats).toHaveProperty('userId');
|
||||||
|
expect(userHeats).toHaveProperty('username');
|
||||||
|
expect(userHeats).toHaveProperty('heats');
|
||||||
|
expect(userHeats.heats).toBeInstanceOf(Array);
|
||||||
|
|
||||||
|
if (userHeats.heats.length > 0) {
|
||||||
|
const heat = userHeats.heats[0];
|
||||||
|
expect(heat).toHaveProperty('division');
|
||||||
|
expect(heat).toHaveProperty('competitionType');
|
||||||
|
expect(heat).toHaveProperty('heatNumber');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by division', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent.slug}/heats/all?divisionId=${testDivision.id}`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).toBe(true);
|
||||||
|
// Check that all heats in all users have the correct division
|
||||||
|
response.body.data.forEach(userHeats => {
|
||||||
|
userHeats.heats.forEach(heat => {
|
||||||
|
expect(heat.division.id).toBe(testDivision.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for any authenticated user (no participant check)', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get(`/api/events/${testEvent2.slug}/heats/all`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
// testEvent2 has no heats, so data should be empty
|
||||||
|
expect(response.body.data).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DELETE /api/events/:slug/heats/:id', () => {
|
||||||
|
let heatToDelete;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Get a heat ID for deletion
|
||||||
|
const heats = await prisma.eventUserHeat.findMany({
|
||||||
|
where: {
|
||||||
|
userId: testUser1.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
heatToDelete = heats[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete user\'s heat', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.delete(`/api/events/${testEvent.slug}/heats/${heatToDelete.id}`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', true);
|
||||||
|
expect(response.body).toHaveProperty('message');
|
||||||
|
|
||||||
|
// Verify heat is deleted
|
||||||
|
const heat = await prisma.eventUserHeat.findUnique({
|
||||||
|
where: { id: heatToDelete.id },
|
||||||
|
});
|
||||||
|
expect(heat).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 404 for non-existent heat', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.delete(`/api/events/${testEvent.slug}/heats/99999`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`)
|
||||||
|
.expect(404);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject deleting another user\'s heat', async () => {
|
||||||
|
// Create a heat for testUser3
|
||||||
|
await prisma.eventParticipant.upsert({
|
||||||
|
where: {
|
||||||
|
userId_eventId: {
|
||||||
|
userId: testUser3.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
userId: testUser3.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
},
|
||||||
|
update: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const heat3 = await prisma.eventUserHeat.create({
|
||||||
|
data: {
|
||||||
|
userId: testUser3.id,
|
||||||
|
eventId: testEvent.id,
|
||||||
|
divisionId: testDivision.id,
|
||||||
|
competitionTypeId: testCompetitionType.id,
|
||||||
|
heatNumber: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.delete(`/api/events/${testEvent.slug}/heats/${heat3.id}`)
|
||||||
|
.set('Authorization', `Bearer ${testToken1}`) // testUser1 tries to delete testUser3's heat
|
||||||
|
.expect(403);
|
||||||
|
|
||||||
|
expect(response.body).toHaveProperty('success', false);
|
||||||
|
expect(response.body.error).toContain('your own heats');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user