diff --git a/backend/tests/accidentRoutes.test.js b/backend/tests/accidentRoutes.test.js new file mode 100644 index 0000000..5355fca --- /dev/null +++ b/backend/tests/accidentRoutes.test.js @@ -0,0 +1,65 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import request from 'supertest'; + +vi.mock('../services/emailService.js', () => ({ + sendActivationEmail: vi.fn().mockResolvedValue(), +})); + +import app from './testApp.js'; +import sequelize from '../database.js'; +import User from '../models/User.js'; +import Club from '../models/Club.js'; +import UserClub from '../models/UserClub.js'; +import Member from '../models/Member.js'; +import DiaryDate from '../models/DiaryDates.js'; + +const registerAndActivate = async (email) => { + const password = 'Test123!'; + await request(app).post('/api/auth/register').send({ email, password }); + const user = await User.findOne({ where: { email } }); + await user.update({ isActive: true }); + return { user, credentials: { email, password } }; +}; + +const loginAndGetToken = async (credentials) => { + const response = await request(app).post('/api/auth/login').send(credentials); + return response.body.token; +}; + +describe('Accident Routes', () => { + beforeEach(async () => { + await sequelize.sync({ force: true }); + }); + + it('legt einen Unfall an und gibt ihn zurück', async () => { + const { user, credentials } = await registerAndActivate('accident@example.com'); + const token = await loginAndGetToken(credentials); + const club = await Club.create({ name: 'Accident Club' }); + await UserClub.create({ userId: user.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }); + const member = await Member.create({ firstName: 'Anna', lastName: 'Accident', clubId: club.id }); + const diaryDate = await DiaryDate.create({ clubId: club.id, date: new Date(), description: 'Training' }); + + const createResponse = await request(app) + .post('/api/accident/add') + .set('Authorization', `Bearer ${token}`) + .send({ clubId: club.id, memberId: member.id, diaryDateId: diaryDate.id, accident: 'Verletzung' }); + + expect(createResponse.status).toBe(201); + + const listResponse = await request(app) + .get(`/api/accident/${club.id}/${diaryDate.id}`) + .set('Authorization', `Bearer ${token}`); + + expect(listResponse.status).toBe(200); + expect(listResponse.body).toHaveLength(1); + expect(listResponse.body[0]).toMatchObject({ firstName: 'Anna' }); + }); + + it('verhindert Anlage eines Unfalls ohne Authentifizierung', async () => { + const response = await request(app) + .post('/api/accident/add') + .send({ clubId: 1, memberId: 1, diaryDateId: 1, accident: 'Test' }); + + expect(response.status).toBe(401); + }); +}); diff --git a/backend/tests/accidentService.test.js b/backend/tests/accidentService.test.js new file mode 100644 index 0000000..9bb21e1 --- /dev/null +++ b/backend/tests/accidentService.test.js @@ -0,0 +1,63 @@ +import { describe, it, expect, beforeEach } from 'vitest'; + +import sequelize from '../database.js'; +import Accident from '../models/Accident.js'; +import DiaryDate from '../models/DiaryDates.js'; +import Member from '../models/Member.js'; +import User from '../models/User.js'; +import Club from '../models/Club.js'; +import UserClub from '../models/UserClub.js'; +import accidentService from '../services/accidentService.js'; + +vi.mock('../utils/userUtils.js', async () => { + const actual = await vi.importActual('../utils/userUtils.js'); + return { + ...actual, + checkAccess: vi.fn().mockResolvedValue(true), + getUserByToken: vi.fn().mockResolvedValue({ id: 1 }), + }; +}); + +describe('accidentService', () => { + const token = 'test-token'; + + beforeEach(async () => { + await sequelize.sync({ force: true }); + }); + + it('legt einen Unfall-Eintrag an, wenn alle Referenzen vorhanden sind', async () => { + const club = await Club.create({ name: 'Accident Club' }); + const member = await Member.create({ firstName: 'Alice', lastName: 'Accident', clubId: club.id }); + const diaryDate = await DiaryDate.create({ clubId: club.id, date: new Date(), description: 'Training' }); + + const result = await accidentService.createAccident(token, club.id, member.id, diaryDate.id, 'Verstauchung'); + + expect(result).toBeTruthy(); + const stored = await Accident.findOne({ where: { diaryDateId: diaryDate.id } }); + expect(stored).not.toBeNull(); + expect(stored.accident).toBe('Verstauchung'); + }); + + it('wirft Fehler, wenn Member nicht im selben Club ist', async () => { + const club = await Club.create({ name: 'Club A' }); + const otherClub = await Club.create({ name: 'Club B' }); + const member = await Member.create({ firstName: 'Bob', lastName: 'B', clubId: otherClub.id }); + const diaryDate = await DiaryDate.create({ clubId: club.id, date: new Date(), description: 'Training' }); + + await expect( + accidentService.createAccident(token, club.id, member.id, diaryDate.id, 'Sturz') + ).rejects.toThrow('Member not found'); + }); + + it('liefert Unfälle samt Mitgliedern zurück', async () => { + const club = await Club.create({ name: 'Club A' }); + const member = await Member.create({ firstName: 'Clara', lastName: 'Club', clubId: club.id }); + const diaryDate = await DiaryDate.create({ clubId: club.id, date: new Date(), description: 'Training' }); + await Accident.create({ memberId: member.id, diaryDateId: diaryDate.id, accident: 'Verstauchung' }); + + const accidents = await accidentService.getAccidents(token, club.id, diaryDate.id); + + expect(accidents).toHaveLength(1); + expect(accidents[0]).toMatchObject({ firstName: 'Clara', accident: 'Verstauchung' }); + }); +}); diff --git a/backend/tests/activityController.test.js b/backend/tests/activityController.test.js new file mode 100644 index 0000000..a34c49c --- /dev/null +++ b/backend/tests/activityController.test.js @@ -0,0 +1,63 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import request from 'supertest'; + +vi.mock('../services/emailService.js', () => ({ + sendActivationEmail: vi.fn().mockResolvedValue(), +})); + +import app from './testApp.js'; +import sequelize from '../database.js'; +import User from '../models/User.js'; +import Club from '../models/Club.js'; +import UserClub from '../models/UserClub.js'; +import DiaryDate from '../models/DiaryDates.js'; + +const registerAndActivate = async (email) => { + const password = 'Test123!'; + await request(app).post('/api/auth/register').send({ email, password }); + const user = await User.findOne({ where: { email } }); + await user.update({ isActive: true }); + return { user, credentials: { email, password } }; +}; + +const loginAndGetToken = async (credentials) => { + const response = await request(app).post('/api/auth/login').send(credentials); + return response.body.token; +}; + +describe('Activity Routes', () => { + beforeEach(async () => { + await sequelize.sync({ force: true }); + }); + + it('erstellt und liest Aktivitäten', async () => { + const { user, credentials } = await registerAndActivate('activity@example.com'); + const token = await loginAndGetToken(credentials); + const club = await Club.create({ name: 'Activity Club' }); + await UserClub.create({ userId: user.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }); + const diaryDate = await DiaryDate.create({ clubId: club.id, date: new Date(), description: 'Training' }); + + const createResponse = await request(app) + .post('/api/activities/add') + .set('Authorization', `Bearer ${token}`) + .send({ diaryDateId: diaryDate.id, description: 'Koordinationsübungen' }); + + expect(createResponse.status).toBe(201); + + const listResponse = await request(app) + .get(`/api/activities/${diaryDate.id}`) + .set('Authorization', `Bearer ${token}`); + + expect(listResponse.status).toBe(200); + expect(listResponse.body).toHaveLength(1); + expect(listResponse.body[0]).toMatchObject({ description: 'Koordinationsübungen' }); + }); + + it('verweigert Anlage einer Aktivität ohne Token', async () => { + const response = await request(app) + .post('/api/activities/add') + .send({ diaryDateId: 1, description: 'Test' }); + + expect(response.status).toBe(401); + }); +}); diff --git a/backend/tests/activityService.test.js b/backend/tests/activityService.test.js new file mode 100644 index 0000000..e5fc81f --- /dev/null +++ b/backend/tests/activityService.test.js @@ -0,0 +1,39 @@ +import { describe, it, expect, beforeEach } from 'vitest'; + +import sequelize from '../database.js'; +import Activity from '../models/Activity.js'; +import DiaryDate from '../models/DiaryDates.js'; +import activityController from '../controllers/activityController.js'; + +import { buildMockRequest, buildMockResponse } from './testUtils.js'; + +describe('activityController', () => { + beforeEach(async () => { + await sequelize.sync({ force: true }); + }); + + it('fügt eine Aktivität hinzu', async () => { + const diaryDate = await DiaryDate.create({ clubId: 1, date: new Date(), description: 'Training' }); + const req = buildMockRequest({ body: { diaryDateId: diaryDate.id, description: 'Koordination' } }); + const res = buildMockResponse(); + + await activityController.addActivity(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + const stored = await Activity.findOne({ where: { diaryDateId: diaryDate.id } }); + expect(stored.description).toBe('Koordination'); + }); + + it('liefert Aktivitäten für einen Trainingstag', async () => { + const diaryDate = await DiaryDate.create({ clubId: 1, date: new Date(), description: 'Training' }); + await Activity.create({ diaryDateId: diaryDate.id, description: 'Aufwärmen' }); + + const req = buildMockRequest({ params: { diaryDateId: diaryDate.id } }); + const res = buildMockResponse(); + + await activityController.getActivities(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json.mock.calls[0][0]).toHaveLength(1); + }); +}); diff --git a/backend/tests/testApp.js b/backend/tests/testApp.js index b6e1f98..eefe168 100644 --- a/backend/tests/testApp.js +++ b/backend/tests/testApp.js @@ -1,12 +1,16 @@ import express from 'express'; import authRoutes from '../routes/authRoutes.js'; import permissionRoutes from '../routes/permissionRoutes.js'; +import accidentRoutes from '../routes/accidentRoutes.js'; +import activityRoutes from '../routes/activityRoutes.js'; const app = express(); app.use(express.json()); app.use('/api/auth', authRoutes); app.use('/api/permissions', permissionRoutes); +app.use('/api/accident', accidentRoutes); +app.use('/api/activities', activityRoutes); app.use((err, req, res, next) => { const status = err?.status || err?.statusCode || 500; diff --git a/backend/tests/testUtils.js b/backend/tests/testUtils.js new file mode 100644 index 0000000..8c74925 --- /dev/null +++ b/backend/tests/testUtils.js @@ -0,0 +1,16 @@ +import { vi } from 'vitest'; + +export function buildMockRequest({ body = {}, params = {}, headers = {} } = {}) { + return { + body, + params, + headers, + }; +} + +export function buildMockResponse() { + const res = {}; + res.status = vi.fn().mockReturnValue(res); + res.json = vi.fn().mockReturnValue(res); + return res; +}