const request = require('supertest'); const { app } = require('../../backend/server'); const { cleanDb, seedGame, seedSession, seedSessionGame, seedVote } = require('../helpers/test-utils'); describe('GET /api/votes', () => { let game1, game2, session; beforeEach(() => { cleanDb(); game1 = seedGame({ title: 'Quiplash 3', pack_name: 'Party Pack 7' }); game2 = seedGame({ title: 'Drawful 2', pack_name: 'Party Pack 3' }); session = seedSession({ is_active: 1 }); seedSessionGame(session.id, game1.id); seedSessionGame(session.id, game2.id); }); test('returns all votes with pagination metadata', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game1.id, 'user2', 'down', '2026-03-15T20:02:00.000Z'); const res = await request(app).get('/api/votes'); expect(res.status).toBe(200); expect(res.body.votes).toHaveLength(2); expect(res.body.pagination).toEqual({ page: 1, limit: 50, total: 2, total_pages: 1, }); }); test('returns vote_type as "up"/"down" not raw integers', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game1.id, 'user2', 'down', '2026-03-15T20:02:00.000Z'); const res = await request(app).get('/api/votes'); const types = res.body.votes.map((v) => v.vote_type); expect(types).toContain('up'); expect(types).toContain('down'); expect(types).not.toContain(1); expect(types).not.toContain(-1); }); test('includes game_title and pack_name via join', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); const res = await request(app).get('/api/votes'); expect(res.body.votes[0].game_title).toBe('Quiplash 3'); expect(res.body.votes[0].pack_name).toBe('Party Pack 7'); }); test('filters by session_id', async () => { const session2 = seedSession({ is_active: 0 }); seedSessionGame(session2.id, game1.id); seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session2.id, game1.id, 'user2', 'up', '2026-03-15T21:01:00.000Z'); const res = await request(app).get(`/api/votes?session_id=${session.id}`); expect(res.body.votes).toHaveLength(1); expect(res.body.votes[0].session_id).toBe(session.id); expect(res.body.pagination.total).toBe(1); }); test('filters by game_id', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game2.id, 'user2', 'down', '2026-03-15T20:02:00.000Z'); const res = await request(app).get(`/api/votes?game_id=${game1.id}`); expect(res.body.votes).toHaveLength(1); expect(res.body.votes[0].game_id).toBe(game1.id); }); test('filters by username', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game1.id, 'user2', 'down', '2026-03-15T20:02:00.000Z'); const res = await request(app).get('/api/votes?username=user1'); expect(res.body.votes).toHaveLength(1); expect(res.body.votes[0].username).toBe('user1'); }); test('filters by vote_type', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game1.id, 'user2', 'down', '2026-03-15T20:02:00.000Z'); const res = await request(app).get('/api/votes?vote_type=up'); expect(res.body.votes).toHaveLength(1); expect(res.body.votes[0].vote_type).toBe('up'); }); test('combines multiple filters', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game1.id, 'user2', 'down', '2026-03-15T20:02:00.000Z'); seedVote(session.id, game2.id, 'user1', 'up', '2026-03-15T20:03:00.000Z'); const res = await request(app).get( `/api/votes?game_id=${game1.id}&username=user1` ); expect(res.body.votes).toHaveLength(1); expect(res.body.votes[0].username).toBe('user1'); expect(res.body.votes[0].game_id).toBe(game1.id); }); test('respects page and limit', async () => { for (let i = 0; i < 5; i++) { seedVote(session.id, game1.id, `user${i}`, 'up', `2026-03-15T20:0${i}:00.000Z`); } const res = await request(app).get('/api/votes?page=2&limit=2'); expect(res.body.votes).toHaveLength(2); expect(res.body.pagination).toEqual({ page: 2, limit: 2, total: 5, total_pages: 3, }); }); test('caps limit at 100', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); const res = await request(app).get('/api/votes?limit=500'); expect(res.body.pagination.limit).toBe(100); }); test('returns 200 with empty array when no votes match', async () => { const res = await request(app).get('/api/votes?username=nonexistent'); expect(res.status).toBe(200); expect(res.body.votes).toEqual([]); expect(res.body.pagination.total).toBe(0); }); test('returns 400 for invalid session_id', async () => { const res = await request(app).get('/api/votes?session_id=abc'); expect(res.status).toBe(400); }); test('returns 400 for invalid vote_type', async () => { const res = await request(app).get('/api/votes?vote_type=maybe'); expect(res.status).toBe(400); }); test('orders by timestamp descending', async () => { seedVote(session.id, game1.id, 'user1', 'up', '2026-03-15T20:01:00.000Z'); seedVote(session.id, game1.id, 'user2', 'down', '2026-03-15T20:05:00.000Z'); const res = await request(app).get('/api/votes'); const timestamps = res.body.votes.map((v) => v.timestamp); expect(timestamps[0]).toBe('2026-03-15T20:05:00.000Z'); expect(timestamps[1]).toBe('2026-03-15T20:01:00.000Z'); }); });