const request = require('supertest'); const mongoose = require('mongoose'); const { connectMongo, disconnectMongo, buildApp } = require('../../helpers/createTestApp'); const { generateToken, mockUser, TEST_USER_ID, TEST_USERNAME } = require('../../helpers/auth'); jest.mock('../../../src/services', () => ({ ArticleService: {}, CommentService: {}, ProductService: {}, SkydiverProfileService: {}, TagService: {}, UserService: { getUserById: jest.fn(), getUserByUsername: jest.fn(), getUserByEmail: jest.fn(), }, ClanService: {}, MemberService: {}, })); const { UserService } = require('../../../src/services'); let app; let token; let testJump; beforeAll(async () => { await connectMongo(); app = buildApp('/api/skydive/jumps', require('../../../src/routes/api/skydive/jumps.routes')); token = generateToken(); UserService.getUserById.mockResolvedValue(mockUser); UserService.getUserByUsername.mockResolvedValue(null); const Jump = mongoose.model('Jump'); testJump = await Jump.create({ numero: 9001, date: new Date('2024-06-15'), lieu: 'Perris', oaci: 'KPRB', aeronef: 'Otter', imat: 'N123DZ', hauteur: 4000, voile: 'Pilot', taille: 188, categorie: 'Formation', module: 'RW', participants: 4, author: TEST_USER_ID, }); }); afterAll(async () => { await disconnectMongo(); }); describe('GET /api/skydive/jumps — auth protection', () => { it('returns 401 without token', async () => { const res = await request(app).get('/api/skydive/jumps'); expect(res.status).toBe(401); }); it('returns 401 with an invalid token', async () => { const res = await request(app) .get('/api/skydive/jumps') .set('Authorization', 'Token bad.token.value'); expect(res.status).toBe(401); }); }); describe('GET /api/skydive/jumps — list', () => { it('returns jump list with correct structure', async () => { const res = await request(app) .get('/api/skydive/jumps') .set('Authorization', `Token ${token}`); expect(res.status).toBe(200); expect(res.body).toHaveProperty('jumps'); expect(res.body).toHaveProperty('jumpsCount'); expect(Array.isArray(res.body.jumps)).toBe(true); }); it('includes the seeded jump', async () => { const res = await request(app) .get('/api/skydive/jumps') .set('Authorization', `Token ${token}`); expect(res.body.jumpsCount).toBeGreaterThanOrEqual(1); const found = res.body.jumps.find(j => j.numero === 9001); expect(found).toBeDefined(); expect(found.lieu).toBe('Perris'); }); it('filters by title search', async () => { const res = await request(app) .get('/api/skydive/jumps?title=nomatch_xyz') .set('Authorization', `Token ${token}`); expect(res.status).toBe(200); expect(res.body.jumpsCount).toBe(0); }); }); describe('POST /api/skydive/jumps — create', () => { it('creates a new jump', async () => { const res = await request(app) .post('/api/skydive/jumps') .set('Authorization', `Token ${token}`) .send({ jump: { numero: 9002, date: '2024-07-01', lieu: 'Empuriabrava', oaci: 'LEEC', aeronef: 'Caravan', hauteur: 4200, voile: 'Pilot', taille: 188, categorie: 'Formation', module: 'RW', participants: 8, } }); expect(res.status).toBe(200); expect(res.body).toHaveProperty('jump'); expect(res.body.jump.numero).toBe(9002); expect(res.body.jump.lieu).toBe('Empuriabrava'); }); it('returns 401 when UserService returns null', async () => { UserService.getUserById.mockResolvedValueOnce(null); const res = await request(app) .post('/api/skydive/jumps') .set('Authorization', `Token ${token}`) .send({ jump: { numero: 9999, lieu: 'Test' } }); expect(res.status).toBe(401); }); }); describe('GET /api/skydive/jumps/:slug — single jump', () => { it('returns the jump for its author', async () => { const res = await request(app) .get(`/api/skydive/jumps/${testJump.slug}`) .set('Authorization', `Token ${token}`); expect(res.status).toBe(200); expect(res.body.jump.numero).toBe(9001); }); it('returns 404 for unknown slug', async () => { const res = await request(app) .get('/api/skydive/jumps/slug-does-not-exist') .set('Authorization', `Token ${token}`); expect(res.status).toBe(404); }); it('returns 403 if authenticated user is not the author', async () => { const otherToken = generateToken('b0000000-0000-0000-0000-000000000002', 'other_user'); UserService.getUserById.mockResolvedValueOnce({ id: 'b0000000-0000-0000-0000-000000000002', username: 'other_user', }); const res = await request(app) .get(`/api/skydive/jumps/${testJump.slug}`) .set('Authorization', `Token ${otherToken}`); expect(res.status).toBe(403); }); }); describe('PUT /api/skydive/jumps/:slug — update', () => { it('updates the jump', async () => { const res = await request(app) .put(`/api/skydive/jumps/${testJump.slug}`) .set('Authorization', `Token ${token}`) .send({ jump: { lieu: 'Spa-La-Sauvenière' } }); expect(res.status).toBe(200); expect(res.body.jump.lieu).toBe('Spa-La-Sauvenière'); }); it('returns 403 if not the author', async () => { const otherToken = generateToken('b0000000-0000-0000-0000-000000000002', 'other_user'); UserService.getUserById.mockResolvedValueOnce({ id: 'b0000000-0000-0000-0000-000000000002', username: 'other_user', }); const res = await request(app) .put(`/api/skydive/jumps/${testJump.slug}`) .set('Authorization', `Token ${otherToken}`) .send({ jump: { lieu: 'Hacker' } }); expect(res.status).toBe(403); }); }); describe('DELETE /api/skydive/jumps/:slug — delete', () => { it('returns 403 if not the author', async () => { const otherToken = generateToken('b0000000-0000-0000-0000-000000000002', 'other_user'); UserService.getUserById.mockResolvedValueOnce({ id: 'b0000000-0000-0000-0000-000000000002', username: 'other_user', }); const res = await request(app) .delete(`/api/skydive/jumps/${testJump.slug}`) .set('Authorization', `Token ${otherToken}`); expect(res.status).toBe(403); }); it('deletes the jump if author', async () => { const Jump = mongoose.model('Jump'); const toDelete = await Jump.create({ numero: 9003, date: new Date(), lieu: 'ToDelete', autor: TEST_USER_ID, author: TEST_USER_ID, }); const res = await request(app) .delete(`/api/skydive/jumps/${toDelete.slug}`) .set('Authorization', `Token ${token}`); expect(res.status).toBe(200); expect(res.body.deleted).toBe(true); }); });