Files
spotlightcam/backend/src/__tests__/matches.test.js

495 lines
16 KiB
JavaScript
Raw Normal View History

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, testEvent, testToken1, testToken2, testToken3;
let testMatch;
// Setup test data
beforeAll(async () => {
// Clean up
await prisma.rating.deleteMany({});
await prisma.message.deleteMany({});
await prisma.match.deleteMany({});
await prisma.chatRoom.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_moves',
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 });
// Create test event
testEvent = await prisma.event.create({
data: {
name: 'Test Dance Festival',
slug: 'test-dance-festival',
location: 'Test City',
startDate: new Date('2025-06-01'),
endDate: new Date('2025-06-03'),
description: 'Test event',
worldsdcId: '12345',
},
});
// Add users as participants
await prisma.eventParticipant.createMany({
data: [
{ userId: testUser1.id, eventId: testEvent.id },
{ userId: testUser2.id, eventId: testEvent.id },
{ userId: testUser3.id, eventId: testEvent.id },
],
});
});
afterAll(async () => {
// Clean up
await prisma.rating.deleteMany({});
await prisma.message.deleteMany({});
await prisma.match.deleteMany({});
await prisma.chatRoom.deleteMany({});
await prisma.eventParticipant.deleteMany({});
await prisma.event.deleteMany({});
await prisma.user.deleteMany({});
await prisma.$disconnect();
});
describe('Matches API Tests', () => {
describe('POST /api/matches', () => {
it('should create a match request successfully', async () => {
const response = await request(app)
.post('/api/matches')
.set('Authorization', `Bearer ${testToken1}`)
.send({
targetUserId: testUser2.id,
eventSlug: testEvent.slug,
})
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('slug');
expect(response.body.data).toHaveProperty('status', 'pending');
expect(response.body.data.slug).toMatch(/^[a-z0-9]+$/); // CUID format
// Save for later tests
testMatch = response.body.data;
});
it('should reject match request without authentication', async () => {
const response = await request(app)
.post('/api/matches')
.send({
targetUserId: testUser2.id,
eventSlug: testEvent.slug,
})
.expect('Content-Type', /json/)
.expect(401);
expect(response.body).toHaveProperty('success', false);
});
it('should reject match request to yourself', async () => {
const response = await request(app)
.post('/api/matches')
.set('Authorization', `Bearer ${testToken1}`)
.send({
targetUserId: testUser1.id, // Same as authenticated user
eventSlug: testEvent.slug,
})
.expect('Content-Type', /json/)
.expect(400);
expect(response.body).toHaveProperty('success', false);
expect(response.body.error).toContain('yourself');
});
it('should reject match request with invalid event', async () => {
const response = await request(app)
.post('/api/matches')
.set('Authorization', `Bearer ${testToken1}`)
.send({
targetUserId: testUser2.id,
eventSlug: 'non-existent-event',
})
.expect('Content-Type', /json/)
.expect(404);
expect(response.body).toHaveProperty('success', false);
});
});
describe('GET /api/matches', () => {
it('should list all matches for authenticated user', async () => {
const response = await request(app)
.get('/api/matches')
.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);
const match = response.body.data[0];
expect(match).toHaveProperty('slug');
expect(match).toHaveProperty('partner');
expect(match).toHaveProperty('event');
expect(match).toHaveProperty('status');
});
it('should filter matches by event slug', async () => {
const response = await request(app)
.get(`/api/matches?eventSlug=${testEvent.slug}`)
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toBeInstanceOf(Array);
if (response.body.data.length > 0) {
expect(response.body.data[0].event.slug).toBe(testEvent.slug);
}
});
it('should filter matches by status', async () => {
const response = await request(app)
.get('/api/matches?status=pending')
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toBeInstanceOf(Array);
response.body.data.forEach(match => {
expect(match.status).toBe('pending');
});
});
});
describe('GET /api/matches/:slug', () => {
it('should get match details by slug', async () => {
const response = await request(app)
.get(`/api/matches/${testMatch.slug}`)
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('slug', testMatch.slug);
expect(response.body.data).toHaveProperty('partner');
expect(response.body.data).toHaveProperty('event');
expect(response.body.data).toHaveProperty('hasRated', false);
expect(response.body.data).toHaveProperty('isInitiator');
});
it('should reject access to match by non-participant', async () => {
// Create third user
const testUser3 = await prisma.user.create({
data: {
username: 'outsider',
email: 'outsider@example.com',
passwordHash: await hashPassword('password123'),
emailVerified: true,
},
});
const testToken3 = generateToken({ userId: testUser3.id });
const response = await request(app)
.get(`/api/matches/${testMatch.slug}`)
.set('Authorization', `Bearer ${testToken3}`)
.expect('Content-Type', /json/)
.expect(403);
expect(response.body).toHaveProperty('success', false);
// Clean up
await prisma.user.delete({ where: { id: testUser3.id } });
});
it('should return 404 for non-existent match slug', async () => {
const response = await request(app)
.get('/api/matches/nonexistentslug123')
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(404);
expect(response.body).toHaveProperty('success', false);
});
});
describe('PUT /api/matches/:slug/accept', () => {
it('should accept a match request', async () => {
const response = await request(app)
.put(`/api/matches/${testMatch.slug}/accept`)
.set('Authorization', `Bearer ${testToken2}`) // User 2 accepts
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('status', 'accepted');
expect(response.body.data).toHaveProperty('roomId');
});
it('should reject accepting already accepted match', async () => {
const response = await request(app)
.put(`/api/matches/${testMatch.slug}/accept`)
.set('Authorization', `Bearer ${testToken2}`)
.expect('Content-Type', /json/)
.expect(400);
expect(response.body).toHaveProperty('success', false);
expect(response.body.error).toContain('already accepted');
});
it('should reject accepting match by initiator', async () => {
// Create new match for this test (with testUser3)
const newMatchRes = await request(app)
.post('/api/matches')
.set('Authorization', `Bearer ${testToken1}`)
.send({
targetUserId: testUser3.id,
eventSlug: testEvent.slug,
})
.expect(201);
const newMatch = newMatchRes.body.data;
expect(newMatch).toHaveProperty('slug');
const response = await request(app)
.put(`/api/matches/${newMatch.slug}/accept`)
.set('Authorization', `Bearer ${testToken1}`) // Initiator tries to accept
.expect('Content-Type', /json/)
.expect(403);
expect(response.body).toHaveProperty('success', false);
// Clean up
await prisma.match.delete({ where: { slug: newMatch.slug } });
});
});
describe('GET /api/matches/:slug/messages', () => {
it('should get match messages (empty initially)', async () => {
const response = await request(app)
.get(`/api/matches/${testMatch.slug}/messages`)
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toBeInstanceOf(Array);
});
});
describe('DELETE /api/matches/:slug', () => {
it('should delete/reject a match request', async () => {
// Create new match for deletion test (with a different user)
const newMatchRes = await request(app)
.post('/api/matches')
.set('Authorization', `Bearer ${testToken2}`)
.send({
targetUserId: testUser3.id,
eventSlug: testEvent.slug,
})
.expect(201);
const newMatch = newMatchRes.body.data;
expect(newMatch).toHaveProperty('slug');
const response = await request(app)
.delete(`/api/matches/${newMatch.slug}`)
.set('Authorization', `Bearer ${testToken2}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body).toHaveProperty('message');
// Verify deletion
const match = await prisma.match.findUnique({
where: { slug: newMatch.slug },
});
expect(match).toBeNull();
});
});
});
describe('Ratings API Tests', () => {
describe('POST /api/matches/:slug/ratings', () => {
it('should create a rating successfully', async () => {
const response = await request(app)
.post(`/api/matches/${testMatch.slug}/ratings`)
.set('Authorization', `Bearer ${testToken1}`)
.send({
score: 5,
comment: 'Great partner!',
wouldCollaborateAgain: true,
})
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('score', 5);
expect(response.body.data).toHaveProperty('comment', 'Great partner!');
expect(response.body.data).toHaveProperty('wouldCollaborateAgain', true);
});
it('should reject duplicate rating from same user', async () => {
const response = await request(app)
.post(`/api/matches/${testMatch.slug}/ratings`)
.set('Authorization', `Bearer ${testToken1}`) // Same user tries again
.send({
score: 4,
comment: 'Second rating',
wouldCollaborateAgain: false,
})
.expect('Content-Type', /json/)
.expect(400);
expect(response.body).toHaveProperty('success', false);
expect(response.body.error).toContain('already rated');
});
it('should reject rating with invalid score', async () => {
const response = await request(app)
.post(`/api/matches/${testMatch.slug}/ratings`)
.set('Authorization', `Bearer ${testToken2}`)
.send({
score: 6, // Invalid (must be 1-5)
comment: 'Test',
wouldCollaborateAgain: true,
})
.expect('Content-Type', /json/)
.expect(400);
expect(response.body).toHaveProperty('success', false);
});
it('should create rating without comment (optional)', async () => {
const response = await request(app)
.post(`/api/matches/${testMatch.slug}/ratings`)
.set('Authorization', `Bearer ${testToken2}`)
.send({
score: 4,
wouldCollaborateAgain: true,
})
.expect('Content-Type', /json/)
.expect(201);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('score', 4);
expect(response.body.data).toHaveProperty('comment', null);
});
it('should auto-complete match when both users rate', async () => {
// Both users have now rated, check match status
const matchRes = await request(app)
.get(`/api/matches/${testMatch.slug}`)
.set('Authorization', `Bearer ${testToken1}`)
.expect(200);
expect(matchRes.body.data.status).toBe('completed');
});
it('should update hasRated flag after rating', async () => {
const response = await request(app)
.get(`/api/matches/${testMatch.slug}`)
.set('Authorization', `Bearer ${testToken1}`)
.expect(200);
expect(response.body.data.hasRated).toBe(true);
});
});
describe('GET /api/users/:username/ratings', () => {
it('should get user ratings', async () => {
const response = await request(app)
.get(`/api/users/${testUser2.username}/ratings`)
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('success', true);
expect(response.body.data).toHaveProperty('username', testUser2.username);
expect(response.body.data).toHaveProperty('averageRating');
expect(response.body.data).toHaveProperty('ratingsCount');
expect(response.body.data).toHaveProperty('ratings');
expect(response.body.data.ratings).toBeInstanceOf(Array);
if (response.body.data.ratings.length > 0) {
const rating = response.body.data.ratings[0];
expect(rating).toHaveProperty('score');
expect(rating).toHaveProperty('rater');
expect(rating).toHaveProperty('event');
expect(rating).toHaveProperty('createdAt');
}
});
it('should calculate average rating correctly', async () => {
const response = await request(app)
.get(`/api/users/${testUser2.username}/ratings`)
.set('Authorization', `Bearer ${testToken1}`)
.expect(200);
const { averageRating, ratingsCount } = response.body.data;
if (ratingsCount > 0) {
expect(parseFloat(averageRating)).toBeGreaterThan(0);
expect(parseFloat(averageRating)).toBeLessThanOrEqual(5);
}
});
it('should return 404 for non-existent user', async () => {
const response = await request(app)
.get('/api/users/nonexistentuser/ratings')
.set('Authorization', `Bearer ${testToken1}`)
.expect('Content-Type', /json/)
.expect(404);
expect(response.body).toHaveProperty('success', false);
});
});
});