From a8470145a0de551d967b595235d93f6d2574aa80 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 4 Feb 2026 11:44:23 +0100 Subject: [PATCH] refactor(tests): remove obsolete test files and clean up package.json - Deleted outdated test files for activity, API log, authentication, authorization, and club functionalities to streamline the test suite. - Retained the cleanup script in package.json while removing unnecessary test dependencies, optimizing the development environment. --- backend/package.json | 6 +- backend/tests/activityController.test.js | 63 -- backend/tests/activityService.test.js | 41 - backend/tests/apiLogRoutes.test.js | 92 --- backend/tests/apiLogService.test.js | 72 -- backend/tests/authMiddleware.test.js | 60 -- backend/tests/authRoutes.test.js | 161 ---- backend/tests/authService.test.js | 99 --- backend/tests/authorizationMiddleware.test.js | 68 -- backend/tests/clubRoutes.test.js | 135 ---- backend/tests/clubService.test.js | 90 --- backend/tests/clubTeamRoutes.test.js | 119 --- backend/tests/clubTeamService.test.js | 104 --- backend/tests/diaryDateActivityRoutes.test.js | 117 --- .../tests/diaryDateActivityService.test.js | 183 ----- .../tests/diaryMemberActivityRoutes.test.js | 129 ---- backend/tests/diaryNoteRoutes.test.js | 106 --- backend/tests/diaryRoutes.test.js | 164 ---- backend/tests/diaryService.test.js | 134 ---- backend/tests/diaryTagRoutes.test.js | 80 -- backend/tests/groupRoutes.test.js | 78 -- backend/tests/groupService.test.js | 57 -- backend/tests/matchRoutes.test.js | 85 --- backend/tests/matchService.test.js | 83 -- backend/tests/memberActivityRoutes.test.js | 127 ---- backend/tests/memberNoteRoutes.test.js | 80 -- backend/tests/memberRoutes.test.js | 103 --- backend/tests/memberService.test.js | 110 --- .../tests/memberTransferConfigRoutes.test.js | 77 -- .../tests/memberTransferConfigService.test.js | 62 -- backend/tests/memberTransferService.test.js | 101 --- .../myTischtennisFetchLogService.test.js | 38 - backend/tests/myTischtennisRoutes.test.js | 92 --- backend/tests/myTischtennisService.test.js | 70 -- backend/tests/myTischtennisUrlRoutes.test.js | 90 --- backend/tests/pdfParserService.test.js | 124 --- backend/tests/permissionRoutes.test.js | 159 ---- backend/tests/permissionService.test.js | 128 ---- .../tests/predefinedActivityRoutes.test.js | 94 --- .../tests/predefinedActivityService.test.js | 89 --- backend/tests/schedulerService.test.js | 125 ---- backend/tests/seasonRoutes.test.js | 94 --- backend/tests/seasonService.test.js | 50 -- backend/tests/sessionRoutes.test.js | 98 --- backend/tests/setupTestEnv.js | 15 - backend/tests/teamDocumentRoutes.test.js | 109 --- backend/tests/teamDocumentService.test.js | 102 --- backend/tests/teamRoutes.test.js | 90 --- backend/tests/teamService.test.js | 64 -- backend/tests/testApp.js | 64 -- backend/tests/testUtils.js | 16 - backend/tests/tournamentRoutes.test.js | 84 --- backend/tests/tournamentService.test.js | 706 ------------------ backend/tests/utils/factories.js | 123 --- backend/tests/utils/routeAuthMocks.js | 107 --- 55 files changed, 1 insertion(+), 5716 deletions(-) delete mode 100644 backend/tests/activityController.test.js delete mode 100644 backend/tests/activityService.test.js delete mode 100644 backend/tests/apiLogRoutes.test.js delete mode 100644 backend/tests/apiLogService.test.js delete mode 100644 backend/tests/authMiddleware.test.js delete mode 100644 backend/tests/authRoutes.test.js delete mode 100644 backend/tests/authService.test.js delete mode 100644 backend/tests/authorizationMiddleware.test.js delete mode 100644 backend/tests/clubRoutes.test.js delete mode 100644 backend/tests/clubService.test.js delete mode 100644 backend/tests/clubTeamRoutes.test.js delete mode 100644 backend/tests/clubTeamService.test.js delete mode 100644 backend/tests/diaryDateActivityRoutes.test.js delete mode 100644 backend/tests/diaryDateActivityService.test.js delete mode 100644 backend/tests/diaryMemberActivityRoutes.test.js delete mode 100644 backend/tests/diaryNoteRoutes.test.js delete mode 100644 backend/tests/diaryRoutes.test.js delete mode 100644 backend/tests/diaryService.test.js delete mode 100644 backend/tests/diaryTagRoutes.test.js delete mode 100644 backend/tests/groupRoutes.test.js delete mode 100644 backend/tests/groupService.test.js delete mode 100644 backend/tests/matchRoutes.test.js delete mode 100644 backend/tests/matchService.test.js delete mode 100644 backend/tests/memberActivityRoutes.test.js delete mode 100644 backend/tests/memberNoteRoutes.test.js delete mode 100644 backend/tests/memberRoutes.test.js delete mode 100644 backend/tests/memberService.test.js delete mode 100644 backend/tests/memberTransferConfigRoutes.test.js delete mode 100644 backend/tests/memberTransferConfigService.test.js delete mode 100644 backend/tests/memberTransferService.test.js delete mode 100644 backend/tests/myTischtennisFetchLogService.test.js delete mode 100644 backend/tests/myTischtennisRoutes.test.js delete mode 100644 backend/tests/myTischtennisService.test.js delete mode 100644 backend/tests/myTischtennisUrlRoutes.test.js delete mode 100644 backend/tests/pdfParserService.test.js delete mode 100644 backend/tests/permissionRoutes.test.js delete mode 100644 backend/tests/permissionService.test.js delete mode 100644 backend/tests/predefinedActivityRoutes.test.js delete mode 100644 backend/tests/predefinedActivityService.test.js delete mode 100644 backend/tests/schedulerService.test.js delete mode 100644 backend/tests/seasonRoutes.test.js delete mode 100644 backend/tests/seasonService.test.js delete mode 100644 backend/tests/sessionRoutes.test.js delete mode 100644 backend/tests/setupTestEnv.js delete mode 100644 backend/tests/teamDocumentRoutes.test.js delete mode 100644 backend/tests/teamDocumentService.test.js delete mode 100644 backend/tests/teamRoutes.test.js delete mode 100644 backend/tests/teamService.test.js delete mode 100644 backend/tests/testApp.js delete mode 100644 backend/tests/testUtils.js delete mode 100644 backend/tests/tournamentRoutes.test.js delete mode 100644 backend/tests/tournamentService.test.js delete mode 100644 backend/tests/utils/factories.js delete mode 100644 backend/tests/utils/routeAuthMocks.js diff --git a/backend/package.json b/backend/package.json index 9da5335..b35959e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,8 +7,7 @@ "postinstall": "cd ../frontend && npm install && npm run build", "dev": "nodemon server.js", "cleanup:usertoken": "node ./scripts/cleanupUserTokenKeys.js", - "cleanup:indexes": "node ./scripts/cleanupAllIndexes.js", - "test": "cross-env NODE_ENV=test vitest run" + "cleanup:indexes": "node ./scripts/cleanupAllIndexes.js" }, "keywords": [], "author": "", @@ -39,9 +38,6 @@ "devDependencies": { "cross-env": "^7.0.3", "nodemon": "^3.1.4", - "sqlite3": "^5.0.2", - "supertest": "^7.1.1", - "vitest": "^4.0.8", "vue-eslint-parser": "9.4.3" } } diff --git a/backend/tests/activityController.test.js b/backend/tests/activityController.test.js deleted file mode 100644 index a34c49c..0000000 --- a/backend/tests/activityController.test.js +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index a4b8515..0000000 --- a/backend/tests/activityService.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import { addActivity, getActivities } from '../controllers/activityController.js'; - -import { buildMockRequest, buildMockResponse } from './testUtils.js'; -import Activity from '../models/Activity.js'; -import { createClub, createDiaryDate } from './utils/factories.js'; - -describe('activityController', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('fügt eine Aktivität hinzu', async () => { - const club = await createClub({ name: 'Activity Club' }); - const diaryDate = await createDiaryDate(club.id); - const req = buildMockRequest({ body: { diaryDateId: diaryDate.id, description: 'Koordination' } }); - const res = buildMockResponse(); - - await 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 club = await createClub({ name: 'Activity Club' }); - const diaryDate = await createDiaryDate(club.id); - await Activity.create({ diaryDateId: diaryDate.id, description: 'Aufwärmen' }); - - const req = buildMockRequest({ params: { diaryDateId: diaryDate.id } }); - const res = buildMockResponse(); - - await getActivities(req, res); - - expect(res.status).toHaveBeenCalledWith(200); - expect(res.json.mock.calls[0][0]).toHaveLength(1); - }); -}); diff --git a/backend/tests/apiLogRoutes.test.js b/backend/tests/apiLogRoutes.test.js deleted file mode 100644 index 913dce3..0000000 --- a/backend/tests/apiLogRoutes.test.js +++ /dev/null @@ -1,92 +0,0 @@ -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 ApiLog from '../models/ApiLog.js'; -import User from '../models/User.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('API Log Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('listet Logs auf und filtert sie', async () => { - const { user, credentials } = await registerAndActivate('logs@example.com'); - const token = await loginAndGetToken(credentials); - await ApiLog.bulkCreate([ - { method: 'GET', path: '/foo', statusCode: 200, logType: 'api_request' }, - { method: 'POST', path: '/bar', statusCode: 500, logType: 'api_request' } - ]); - - const response = await request(app) - .get('/api/logs?method=POST') - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(200); - expect(response.body.success).toBe(true); - expect(response.body.data.total).toBe(1); - expect(response.body.data.logs[0].path).toBe('/bar'); - }); - - it('liefert einen Logeintrag per ID', async () => { - const { credentials } = await registerAndActivate('singlelog@example.com'); - const token = await loginAndGetToken(credentials); - const log = await ApiLog.create({ method: 'GET', path: '/single', logType: 'api_request' }); - - const response = await request(app) - .get(`/api/logs/${log.id}`) - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(200); - expect(response.body.data.id).toBe(log.id); - }); - - it('gibt 404 zurück, wenn ein Log nicht existiert', async () => { - const { credentials } = await registerAndActivate('missinglog@example.com'); - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .get('/api/logs/999') - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(404); - }); - - it('liefert Scheduler-Informationen', async () => { - const { credentials } = await registerAndActivate('scheduler@example.com'); - const token = await loginAndGetToken(credentials); - await ApiLog.create({ - method: 'SCHEDULER', - path: '/scheduler/rating_updates', - statusCode: 200, - responseBody: JSON.stringify({ updatedCount: 2 }), - logType: 'scheduler', - schedulerJobType: 'rating_updates' - }); - - const response = await request(app) - .get('/api/logs/scheduler/last-executions') - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(200); - expect(response.body.data.rating_updates.lastRun).toBeTruthy(); - }); -}); diff --git a/backend/tests/apiLogService.test.js b/backend/tests/apiLogService.test.js deleted file mode 100644 index c9cabaf..0000000 --- a/backend/tests/apiLogService.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import ApiLog from '../models/ApiLog.js'; -import apiLogService from '../services/apiLogService.js'; - -describe('apiLogService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('speichert API-Requests und kürzt lange Felder', async () => { - const longBody = 'x'.repeat(65000); - await apiLogService.logRequest({ - method: 'POST', - path: '/api/test', - requestBody: longBody, - responseBody: longBody, - }); - - const stored = await ApiLog.findOne({ where: { path: '/api/test' } }); - expect(stored).toBeTruthy(); - expect(stored.requestBody.length).toBeLessThanOrEqual(64020); - expect(stored.responseBody.endsWith('(truncated)')).toBe(true); - }); - - it('filtert Logs nach Methode und Status', async () => { - await ApiLog.bulkCreate([ - { method: 'GET', path: '/one', statusCode: 200, logType: 'api_request' }, - { method: 'POST', path: '/two', statusCode: 500, logType: 'api_request' } - ]); - - const result = await apiLogService.getLogs({ method: 'POST', statusCode: 500 }); - - expect(result.total).toBe(1); - expect(result.logs[0].path).toBe('/two'); - }); - - it('liefert Logs paginiert', async () => { - await ApiLog.bulkCreate(new Array(5).fill(null).map((_, idx) => ({ - method: 'GET', - path: `/paged-${idx}`, - statusCode: 200, - logType: 'api_request' - }))); - - const result = await apiLogService.getLogs({ limit: 2, offset: 2 }); - - expect(result.logs).toHaveLength(2); - expect(result.total).toBe(5); - }); - - it('gibt einen Logeintrag per ID zurück', async () => { - const log = await ApiLog.create({ method: 'GET', path: '/id-test', logType: 'api_request' }); - const fetched = await apiLogService.getLogById(log.id); - - expect(fetched).toBeTruthy(); - expect(fetched.path).toBe('/id-test'); - }); - - it('liefert Scheduler-Ausführungen aggregiert', async () => { - await apiLogService.logSchedulerExecution('rating_updates', true, { updatedCount: 3 }, 120, null); - await apiLogService.logSchedulerExecution('match_results', false, { fetchedCount: 2 }, 300, 'Timeout'); - - const results = await apiLogService.getLastSchedulerExecutions(); - - expect(results.rating_updates.lastRun).toBeTruthy(); - expect(results.rating_updates.updatedCount).toBe(3); - expect(results.match_results.success).toBe(false); - expect(results.match_results.errorMessage).toBe('Timeout'); - }); -}); diff --git a/backend/tests/authMiddleware.test.js b/backend/tests/authMiddleware.test.js deleted file mode 100644 index 29395dd..0000000 --- a/backend/tests/authMiddleware.test.js +++ /dev/null @@ -1,60 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import jwt from 'jsonwebtoken'; - -import { authenticate } from '../middleware/authMiddleware.js'; -import sequelize from '../database.js'; -import User from '../models/User.js'; -import UserToken from '../models/UserToken.js'; - -const createRes = () => { - const res = {}; - res.status = vi.fn().mockReturnValue(res); - res.json = vi.fn().mockReturnValue(res); - return res; -}; - -describe('authenticate middleware', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('antwortet mit 401 wenn kein Token vorhanden ist', async () => { - const req = { headers: {} }; - const res = createRes(); - const next = vi.fn(); - - await authenticate(req, res, next); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.json).toHaveBeenCalledWith({ error: 'Unauthorized: Token fehlt' }); - expect(next).not.toHaveBeenCalled(); - }); - - it('verweigert ungültige Tokens', async () => { - const req = { headers: { authorization: 'Bearer invalid' } }; - const res = createRes(); - const next = vi.fn(); - - await authenticate(req, res, next); - - expect(res.status).toHaveBeenCalledWith(401); - expect(res.json).toHaveBeenCalled(); - expect(next).not.toHaveBeenCalled(); - }); - - it('akzeptiert gültige Tokens und ruft next()', async () => { - const user = await User.create({ email: 'middleware@example.com', password: 'Secret!123', isActive: true }); - const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' }); - await UserToken.create({ userId: user.id, token, expiresAt: new Date(Date.now() + 3600000) }); - - const req = { headers: { authorization: `Bearer ${token}` } }; - const res = createRes(); - const next = vi.fn(); - - await authenticate(req, res, next); - - expect(next).toHaveBeenCalledOnce(); - expect(req.user).toMatchObject({ id: user.id }); - expect(res.status).not.toHaveBeenCalled(); - }); -}); diff --git a/backend/tests/authRoutes.test.js b/backend/tests/authRoutes.test.js deleted file mode 100644 index e2c5ea0..0000000 --- a/backend/tests/authRoutes.test.js +++ /dev/null @@ -1,161 +0,0 @@ -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 UserToken from '../models/UserToken.js'; - -const registerPayload = (email = 'test@example.com', password = 'Passwort!123') => ({ email, password }); - -describe('Auth & Permissions Routes', () => { - beforeEach(async () => { - vi.clearAllMocks(); - await sequelize.sync({ force: true }); - }); - - it('registriert einen Nutzer über die API', async () => { - const response = await request(app) - .post('/api/auth/register') - .send(registerPayload()); - - expect(response.status).toBe(201); - expect(response.body.email).toBe('test@example.com'); - const user = await User.findOne({ where: { email: 'test@example.com' } }); - expect(user).not.toBeNull(); - }); - - it('meldet einen aktiven Nutzer an und liefert einen Token', async () => { - const credentials = registerPayload('login@example.com'); - await request(app).post('/api/auth/register').send(credentials); - const user = await User.findOne({ where: { email: credentials.email } }); - await user.update({ isActive: true }); - - const response = await request(app) - .post('/api/auth/login') - .send(credentials); - - expect(response.status).toBe(200); - expect(response.body.token).toBeTruthy(); - }); - - it('verhindert Login ohne Aktivierung', async () => { - const credentials = registerPayload('inactive@example.com'); - await request(app).post('/api/auth/register').send(credentials); - - const response = await request(app) - .post('/api/auth/login') - .send(credentials); - - expect(response.status).toBe(403); - expect(response.body.message || response.body.error).toMatch(/aktiviert/i); - }); - - it('verweigert doppelte Registrierung', async () => { - const payload = registerPayload('duplicate@example.com'); - await request(app).post('/api/auth/register').send(payload); - - const response = await request(app).post('/api/auth/register').send(payload); - - expect(response.status).toBe(409); - expect(response.body.error || response.body.message).toMatch(/bereits/i); - }); - - it('aktiviert einen Benutzer über die API', async () => { - const payload = registerPayload('activate@example.com'); - await request(app).post('/api/auth/register').send(payload); - const user = await User.findOne({ where: { email: payload.email } }); - - const response = await request(app).get(`/api/auth/activate/${user.activationCode}`); - - expect(response.status).toBe(200); - const reloaded = await user.reload(); - expect(reloaded.isActive).toBe(true); - }); - - it('meldet Fehler bei ungültigem Aktivierungscode', async () => { - const response = await request(app).get('/api/auth/activate/invalid-code'); - expect(response.status).toBe(404); - }); - - it('meldet einen Nutzer ab und entfernt den Token', async () => { - const credentials = registerPayload('logout-route@example.com'); - await request(app).post('/api/auth/register').send(credentials); - const user = await User.findOne({ where: { email: credentials.email } }); - await user.update({ isActive: true }); - - const login = await request(app).post('/api/auth/login').send(credentials); - const token = login.body.token; - - const response = await request(app) - .post('/api/auth/logout') - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(200); - const tokenRecord = await UserToken.findOne({ where: { token } }); - expect(tokenRecord).toBeNull(); - }); - - it('ändert Rollen über die Permissions-API (Admin)', async () => { - const ownerPassword = 'OwnerPass!1'; - const memberPassword = 'MemberPass!1'; - - const owner = await User.create({ email: 'owner@example.com', password: ownerPassword, isActive: true }); - const member = await User.create({ email: 'member@example.com', password: memberPassword, isActive: true }); - const club = await Club.create({ name: 'Functional Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const loginResponse = await request(app) - .post('/api/auth/login') - .send({ email: owner.email, password: ownerPassword }); - - const token = loginResponse.body.token; - expect(token).toBeTruthy(); - - const updateResponse = await request(app) - .put(`/api/permissions/${club.id}/user/${member.id}/role`) - .set('Authorization', `Bearer ${token}`) - .send({ role: 'trainer' }); - - expect(updateResponse.status).toBe(200); - const updated = await UserClub.findOne({ where: { userId: member.id, clubId: club.id } }); - expect(updated.role).toBe('trainer'); - }); - - it('verweigert Berechtigungsänderungen ohne ausreichende Rolle', async () => { - const ownerPassword = 'OwnerPass!1'; - const memberPassword = 'MemberPass!1'; - - const owner = await User.create({ email: 'owner2@example.com', password: ownerPassword, isActive: true }); - const member = await User.create({ email: 'member2@example.com', password: memberPassword, isActive: true }); - const club = await Club.create({ name: 'Restricted Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const memberLogin = await request(app) - .post('/api/auth/login') - .send({ email: member.email, password: memberPassword }); - - const token = memberLogin.body.token; - - const response = await request(app) - .put(`/api/permissions/${club.id}/user/${owner.id}/role`) - .set('Authorization', `Bearer ${token}`) - .send({ role: 'trainer' }); - - expect(response.status).toBe(403); - }); -}); diff --git a/backend/tests/authService.test.js b/backend/tests/authService.test.js deleted file mode 100644 index 24e8fdf..0000000 --- a/backend/tests/authService.test.js +++ /dev/null @@ -1,99 +0,0 @@ -import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest'; - -vi.mock('../services/emailService.js', () => ({ - sendActivationEmail: vi.fn().mockResolvedValue(), -})); - -import sequelize from '../database.js'; -import User from '../models/User.js'; -import UserToken from '../models/UserToken.js'; -import { register, activateUser, login, logout } from '../services/authService.js'; - -describe('authService', () => { - beforeAll(async () => { - await sequelize.sync({ force: true }); - }); - - beforeEach(async () => { - await sequelize.truncate({ cascade: true, restartIdentity: true }); - }); - - it('registriert einen neuen Nutzer und sendet eine Aktivierungs-E-Mail', async () => { - const email = 'unit@test.de'; - const password = 'Test123!'; - - const user = await register(email, password); - - expect(user).toBeTruthy(); - const storedUser = await User.findOne({ where: { email } }); - expect(storedUser).not.toBeNull(); - expect(storedUser.password).not.toBe(password); - expect(storedUser.activationCode).toBeTruthy(); - }); - - it('aktiviert einen Benutzer mit gültigem Aktivierungscode', async () => { - const email = 'activate@test.de'; - const password = 'Test123!'; - const user = await register(email, password); - - const activated = await activateUser(user.activationCode); - - expect(activated.isActive).toBe(true); - expect(activated.activationCode).toBeNull(); - }); - - it('meldet einen Fehler bei ungültigem Aktivierungscode', async () => { - await expect(activateUser('does-not-exist')).rejects.toMatchObject({ status: 404 }); - }); - - it('meldet einen Benutzer an und speichert den Token', async () => { - const email = 'login@test.de'; - const password = 'Passwort!7'; - const user = await register(email, password); - await user.update({ isActive: true }); - - const result = await login(email, password); - - expect(result.token).toBeTruthy(); - const tokenRecord = await UserToken.findOne({ where: { token: result.token } }); - expect(tokenRecord).not.toBeNull(); - }); - - it('verhindert Login solange der Account nicht aktiviert wurde', async () => { - const email = 'inactive@test.de'; - const password = 'Test123!'; - await register(email, password); - - await expect(login(email, password)).rejects.toMatchObject({ status: 403 }); - }); - - it('verhindert doppelte Registrierung', async () => { - const email = 'duplicate@test.de'; - const password = 'Test123!'; - await register(email, password); - - await expect(register(email, password)).rejects.toMatchObject({ status: 409 }); - }); - - it('wirft einen Fehler bei ungültigen Anmeldedaten', async () => { - await expect(login('unknown@test.de', 'wrong')).rejects.toMatchObject({ status: 401 }); - }); - - it('löscht den UserToken beim Logout', async () => { - const email = 'logout@test.de'; - const password = 'Passwort!8'; - const user = await register(email, password); - await user.update({ isActive: true }); - const { token } = await login(email, password); - - const response = await logout(token); - - expect(response).toMatchObject({ message: 'Logout erfolgreich' }); - const tokenRecord = await UserToken.findOne({ where: { token } }); - expect(tokenRecord).toBeNull(); - }); - - it('meldet Fehler beim Logout ohne Token', async () => { - await expect(logout()).rejects.toMatchObject({ status: 400 }); - }); -}); diff --git a/backend/tests/authorizationMiddleware.test.js b/backend/tests/authorizationMiddleware.test.js deleted file mode 100644 index dbed86b..0000000 --- a/backend/tests/authorizationMiddleware.test.js +++ /dev/null @@ -1,68 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -import { authorize } from '../middleware/authorizationMiddleware.js'; -import sequelize from '../database.js'; -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import UserClub from '../models/UserClub.js'; - -const createRes = () => { - const res = {}; - res.status = vi.fn().mockReturnValue(res); - res.json = vi.fn().mockReturnValue(res); - return res; -}; - -describe('authorization middleware', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('gibt 400 zurück wenn clubId fehlt', async () => { - const req = { user: { id: 1 }, params: {}, body: {}, query: {} }; - const res = createRes(); - const next = vi.fn(); - - await authorize('members', 'read')(req, res, next); - - expect(res.status).toHaveBeenCalledWith(400); - expect(next).not.toHaveBeenCalled(); - }); - - it('verweigert Zugriff ohne Berechtigungen', async () => { - const owner = await User.create({ email: 'owner-mw@example.com', password: 'Secret!123', isActive: true }); - const member = await User.create({ email: 'member-mw@example.com', password: 'Secret!123', isActive: true }); - const club = await Club.create({ name: 'Middleware Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const req = { user: { id: member.id }, params: { clubId: club.id }, body: {}, query: {} }; - const res = createRes(); - const next = vi.fn(); - - await authorize('permissions', 'write')(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(next).not.toHaveBeenCalled(); - }); - - it('erlaubt Besitzern den Zugriff', async () => { - const owner = await User.create({ email: 'owner-pass@example.com', password: 'Secret!123', isActive: true }); - const club = await Club.create({ name: 'Owner Club' }); - - await UserClub.create({ userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }); - - const req = { user: { id: owner.id }, params: { clubId: club.id }, body: {}, query: {} }; - const res = createRes(); - const next = vi.fn(); - - await authorize('permissions', 'write')(req, res, next); - - expect(next).toHaveBeenCalledOnce(); - expect(res.status).not.toHaveBeenCalled(); - expect(req.userPermissions).toBeTruthy(); - }); -}); diff --git a/backend/tests/clubRoutes.test.js b/backend/tests/clubRoutes.test.js deleted file mode 100644 index 672fc62..0000000 --- a/backend/tests/clubRoutes.test.js +++ /dev/null @@ -1,135 +0,0 @@ -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 UserClub from '../models/UserClub.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Club Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('legt einen Club an und gibt ihn in der Liste zurück', async () => { - const { credentials } = await registerAndActivate('owner@example.com'); - const token = await loginAndGetToken(credentials); - - const createResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Neuer Club' }); - - expect(createResponse.status).toBe(200); - - const listResponse = await request(app) - .get('/api/clubs') - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body.some((club) => club.name === 'Neuer Club')).toBe(true); - }); - - it('verhindert doppelte Club-Namen', async () => { - const { credentials } = await registerAndActivate('duplicate@example.com'); - const token = await loginAndGetToken(credentials); - - await request(app).post('/api/clubs').set(authHeaders(token)).send({ name: 'Doppelclub' }); - const response = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Doppelclub' }); - - expect(response.status).toBe(409); - }); - - it('liefert einen Club nur für Mitglieder', async () => { - const { user: owner, credentials } = await registerAndActivate('clubowner@example.com'); - const ownerToken = await loginAndGetToken(credentials); - const createResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(ownerToken)) - .send({ name: 'Private Club' }); - const clubId = createResponse.body.id; - - const ownerResponse = await request(app) - .get(`/api/clubs/${clubId}`) - .set(authHeaders(ownerToken)); - expect(ownerResponse.status).toBe(200); - - const { credentials: otherCreds } = await registerAndActivate('visitor@example.com'); - const otherToken = await loginAndGetToken(otherCreds); - const otherResponse = await request(app) - .get(`/api/clubs/${clubId}`) - .set(authHeaders(otherToken)); - expect(otherResponse.status).toBe(403); - }); - - it('bearbeitet Zugangs-Anfragen (request/pending/approve/reject)', async () => { - const { user: owner, credentials } = await registerAndActivate('owner2@example.com'); - const ownerToken = await loginAndGetToken(credentials); - const createResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(ownerToken)) - .send({ name: 'Approval Club' }); - const clubId = createResponse.body.id; - - const { user: member, credentials: memberCreds } = await registerAndActivate('member@example.com'); - const memberToken = await loginAndGetToken(memberCreds); - - const requestResponse = await request(app) - .get(`/api/clubs/request/${clubId}`) - .set(authHeaders(memberToken)); - expect(requestResponse.status).toBe(200); - - const pendingResponse = await request(app) - .get(`/api/clubs/pending/${clubId}`) - .set(authHeaders(ownerToken)); - expect(pendingResponse.status).toBe(200); - expect(pendingResponse.body.length).toBe(1); - - const approveResponse = await request(app) - .post('/api/clubs/approve') - .set(authHeaders(ownerToken)) - .send({ clubid: clubId, userid: member.id }); - expect(approveResponse.status).toBe(200); - - const membership = await UserClub.findOne({ where: { userId: member.id, clubId } }); - expect(membership.approved).toBe(true); - - const rejectUser = await registerAndActivate('reject@example.com'); - const rejectToken = await loginAndGetToken(rejectUser.credentials); - await request(app).get(`/api/clubs/request/${clubId}`).set(authHeaders(rejectToken)); - - const rejectResponse = await request(app) - .post('/api/clubs/reject') - .set(authHeaders(ownerToken)) - .send({ clubid: clubId, userid: rejectUser.user.id }); - expect(rejectResponse.status).toBe(200); - - const rejectedMembership = await UserClub.findOne({ where: { userId: rejectUser.user.id, clubId } }); - expect(rejectedMembership).toBeNull(); - }); -}); diff --git a/backend/tests/clubService.test.js b/backend/tests/clubService.test.js deleted file mode 100644 index adefe45..0000000 --- a/backend/tests/clubService.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -import sequelize from '../database.js'; -import Club from '../models/Club.js'; -import User from '../models/User.js'; -import UserClub from '../models/UserClub.js'; -import clubService from '../services/clubService.js'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true) - }; -}); - -import { checkAccess } from '../utils/userUtils.js'; - -describe('clubService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - it('erstellt Clubs und sucht nach Namen', async () => { - await clubService.createClub('Testclub'); - const result = await clubService.findClubByName('test'); - expect(result).toBeTruthy(); - expect(result.name).toContain('Testclub'); - }); - - it('fügt Benutzer einem Club hinzu', async () => { - const user = await User.create({ email: 'club@test.de', password: 'Secret123!', isActive: true }); - const club = await clubService.createClub('Club A'); - - const userClub = await clubService.addUserToClub(user.id, club.id, true); - - expect(userClub.isOwner).toBe(true); - expect(userClub.role).toBe('admin'); - }); - - it('beantragt Club-Zugang und verhindert doppelte Anfragen', async () => { - const user = await User.create({ email: 'member@test.de', password: 'Secret123!', isActive: true }); - const club = await clubService.createClub('Club B'); - - await clubService.requestAccessToClub(user.id, club.id); - await expect(clubService.requestAccessToClub(user.id, club.id)).rejects.toThrow('alreadyrequested'); - }); - - it('genehmigt Club-Zugänge nach erfolgreicher Prüfung', async () => { - const owner = await User.create({ email: 'owner@test.de', password: 'Secret123!', isActive: true }); - const member = await User.create({ email: 'member@test.de', password: 'Secret123!', isActive: true }); - const club = await clubService.createClub('Club C'); - await UserClub.create({ userId: owner.id, clubId: club.id, approved: true, isOwner: true }); - await UserClub.create({ userId: member.id, clubId: club.id, approved: false }); - - await clubService.approveUserClubAccess('token', club.id, member.id); - - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - const updated = await UserClub.findOne({ where: { userId: member.id, clubId: club.id } }); - expect(updated.approved).toBe(true); - }); - - it('liefert ausstehende Freigaben nur bei Zugang', async () => { - const owner = await User.create({ email: 'owner@test.de', password: 'Secret123!', isActive: true }); - const pending = await User.create({ email: 'pending@test.de', password: 'Secret123!', isActive: true }); - const club = await clubService.createClub('Club D'); - await UserClub.create({ userId: owner.id, clubId: club.id, approved: true, isOwner: true }); - await UserClub.create({ userId: pending.id, clubId: club.id, approved: false }); - - const approvals = await clubService.getPendingUserApprovals('token', club.id); - - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - expect(approvals).toHaveLength(1); - expect(approvals[0].userId).toBe(pending.id); - }); - - it('lehnt Club-Zugänge ab', async () => { - const owner = await User.create({ email: 'owner@test.de', password: 'Secret123!', isActive: true }); - const pending = await User.create({ email: 'pending@test.de', password: 'Secret123!', isActive: true }); - const club = await clubService.createClub('Club E'); - await UserClub.create({ userId: owner.id, clubId: club.id, approved: true, isOwner: true }); - await UserClub.create({ userId: pending.id, clubId: club.id, approved: false }); - - await clubService.rejectUserClubAccess('token', club.id, pending.id); - - const record = await UserClub.findOne({ where: { userId: pending.id, clubId: club.id } }); - expect(record).toBeNull(); - }); -}); diff --git a/backend/tests/clubTeamRoutes.test.js b/backend/tests/clubTeamRoutes.test.js deleted file mode 100644 index 737460a..0000000 --- a/backend/tests/clubTeamRoutes.test.js +++ /dev/null @@ -1,119 +0,0 @@ -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 '../models/index.js'; - -import ClubTeam from '../models/ClubTeam.js'; -import League from '../models/League.js'; -import Season from '../models/Season.js'; -import Club from '../models/Club.js'; -import User from '../models/User.js'; -import UserClub from '../models/UserClub.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('ClubTeam Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt ClubTeams und listet sie auf', async () => { - const { user, credentials } = await registerAndActivate('clubteam@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Team Club' }); - - const clubId = clubResponse.body.id; - - const createResponse = await request(app) - .post(`/api/clubteam/club/${clubId}`) - .set(authHeaders(token)) - .send({ name: 'Erstes Team' }); - - expect(createResponse.status).toBe(201); - - const listResponse = await request(app) - .get(`/api/clubteam/club/${clubId}`) - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body[0].name).toBe('Erstes Team'); - }); - - it('aktualisiert und löscht ClubTeams', async () => { - const { credentials } = await registerAndActivate('clubteam2@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Club Zwei' }); - const clubId = clubResponse.body.id; - - const teamResponse = await request(app) - .post(`/api/clubteam/club/${clubId}`) - .set(authHeaders(token)) - .send({ name: 'Team Alt' }); - const teamId = teamResponse.body.id; - - const updateResponse = await request(app) - .put(`/api/clubteam/${teamId}`) - .set(authHeaders(token)) - .send({ name: 'Team Neu' }); - - expect(updateResponse.status).toBe(200); - expect(updateResponse.body.name).toBe('Team Neu'); - - const deleteResponse = await request(app) - .delete(`/api/clubteam/${teamId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - }); - - it('liefert Ligen für einen Club', async () => { - const { credentials } = await registerAndActivate('clubteam3@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Club Drei' }); - const clubId = clubResponse.body.id; - - const season = await Season.create({ season: '2024/2025' }); - await League.create({ name: 'Verbandsliga', clubId, seasonId: season.id, association: 'BV', groupname: 'Nord' }); - - const response = await request(app) - .get(`/api/clubteam/leagues/${clubId}?seasonid=${season.id}`) - .set(authHeaders(token)); - - expect(response.status).toBe(200); - expect(response.body[0].name).toBe('Verbandsliga'); - }); -}); diff --git a/backend/tests/clubTeamService.test.js b/backend/tests/clubTeamService.test.js deleted file mode 100644 index dc46e49..0000000 --- a/backend/tests/clubTeamService.test.js +++ /dev/null @@ -1,104 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import Club from '../models/Club.js'; -import ClubTeam from '../models/ClubTeam.js'; -import League from '../models/League.js'; -import Season from '../models/Season.js'; -import clubTeamService from '../services/clubTeamService.js'; - -describe('clubTeamService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('liefert ClubTeams mit Liga- und Saisoninformationen', async () => { - const club = await Club.create({ name: 'Testclub' }); - const season = await Season.create({ season: '2024/2025' }); - const league = await League.create({ - name: '1. Liga', - clubId: club.id, - seasonId: season.id, - association: 'BV', - groupname: 'Nord', - }); - - await ClubTeam.create({ - name: 'Team A', - clubId: club.id, - seasonId: season.id, - leagueId: league.id, - myTischtennisTeamId: 'MT-1', - }); - - const teams = await clubTeamService.getAllClubTeamsByClub(club.id, season.id); - - expect(teams).toHaveLength(1); - expect(teams[0].league.name).toBe('1. Liga'); - expect(teams[0].season.season).toBe('2024/2025'); - }); - - it('erstellt neue ClubTeams und weist die aktuelle Saison zu', async () => { - const club = await Club.create({ name: 'Neuer Club' }); - - const team = await clubTeamService.createClubTeam({ - name: 'Frisches Team', - clubId: club.id, - }); - - expect(team.id).toBeTruthy(); - expect(team.seasonId).toBeTruthy(); - }); - - it('aktualisiert ClubTeams', async () => { - const club = await Club.create({ name: 'Update Club' }); - const season = await Season.create({ season: '2023/2024' }); - const team = await ClubTeam.create({ - name: 'Team Alt', - clubId: club.id, - seasonId: season.id, - }); - - const updated = await clubTeamService.updateClubTeam(team.id, { name: 'Team Neu' }); - - expect(updated).toBe(true); - const refreshed = await ClubTeam.findByPk(team.id); - expect(refreshed.name).toBe('Team Neu'); - }); - - it('löscht ClubTeams und bestätigt die Entfernung', async () => { - const club = await Club.create({ name: 'Delete Club' }); - const season = await Season.create({ season: '2022/2023' }); - const team = await ClubTeam.create({ - name: 'Team Delete', - clubId: club.id, - seasonId: season.id, - }); - - const removed = await clubTeamService.deleteClubTeam(team.id); - - expect(removed).toBe(true); - const exists = await ClubTeam.findByPk(team.id); - expect(exists).toBeNull(); - }); - - it('liefert Ligen eines Clubs für die aktuelle Saison', async () => { - const club = await Club.create({ name: 'Liga Club' }); - const season = await Season.create({ season: '2025/2026' }); - - await League.create({ - name: 'Regionalliga', - clubId: club.id, - seasonId: season.id, - association: 'TT', - groupname: 'Süd', - }); - - const leagues = await clubTeamService.getLeaguesByClub(club.id, season.id); - - expect(leagues).toHaveLength(1); - expect(leagues[0].name).toBe('Regionalliga'); - }); -}); diff --git a/backend/tests/diaryDateActivityRoutes.test.js b/backend/tests/diaryDateActivityRoutes.test.js deleted file mode 100644 index 37bf017..0000000 --- a/backend/tests/diaryDateActivityRoutes.test.js +++ /dev/null @@ -1,117 +0,0 @@ -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 '../models/index.js'; - -import DiaryDate from '../models/DiaryDates.js'; -import Group from '../models/Group.js'; -import GroupActivity from '../models/GroupActivity.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('DiaryDateActivity Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('verwaltet Aktivitäten einschließlich Reihenfolge und Gruppenaktionen', async () => { - const { credentials } = await registerAndActivate('dda-routes@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Activity Club' }); - const clubId = clubResponse.body.id; - - const diaryDateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token)) - .send({ date: '2026-02-01' }); - const diaryDateId = diaryDateResponse.body.id; - - const timeblockResponse = await request(app) - .post(`/api/diary-date-activities/${clubId}/`) - .set(authHeaders(token)) - .send({ diaryDateId, activity: 'Warmup', isTimeblock: true }); - const timeblockId = timeblockResponse.body.id; - - const activityResponse = await request(app) - .post(`/api/diary-date-activities/${clubId}/`) - .set(authHeaders(token)) - .send({ diaryDateId, activity: 'Topspin üben', duration: '30' }); - const activityId = activityResponse.body.id; - - const updateResponse = await request(app) - .put(`/api/diary-date-activities/${clubId}/${activityId}`) - .set(authHeaders(token)) - .send({ customActivityName: 'Topspin intensiv', duration: 35 }); - - expect(updateResponse.status).toBe(200); - expect(updateResponse.body.predefinedActivityId).toBeTruthy(); - - const reorderResponse = await request(app) - .put(`/api/diary-date-activities/${clubId}/${activityId}/order`) - .set(authHeaders(token)) - .send({ orderId: 1 }); - - expect(reorderResponse.status).toBe(200); - const persisted = await DiaryDateActivity.findByPk(activityId); - expect(persisted.orderId).toBe(1); - - const group = await Group.create({ diaryDateId, name: 'Gruppe A' }); - - const addGroupResponse = await request(app) - .post('/api/diary-date-activities/group') - .set(authHeaders(token)) - .send({ clubId, diaryDateId, groupId: group.id, activity: 'Match', timeblockId }); - - expect(addGroupResponse.status).toBe(201); - const groupActivityId = addGroupResponse.body.id; - - const listResponse = await request(app) - .get(`/api/diary-date-activities/${clubId}/${diaryDateId}`) - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body.length).toBeGreaterThanOrEqual(2); - - const deleteGroupResponse = await request(app) - .delete(`/api/diary-date-activities/group/${clubId}/${groupActivityId}`) - .set(authHeaders(token)); - - expect(deleteGroupResponse.status).toBe(200); - const groupExists = await GroupActivity.findByPk(groupActivityId); - expect(groupExists).toBeNull(); - - const deleteResponse = await request(app) - .delete(`/api/diary-date-activities/${clubId}/${activityId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - }); -}); diff --git a/backend/tests/diaryDateActivityService.test.js b/backend/tests/diaryDateActivityService.test.js deleted file mode 100644 index 7b6b687..0000000 --- a/backend/tests/diaryDateActivityService.test.js +++ /dev/null @@ -1,183 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import diaryDateActivityService from '../services/diaryDateActivityService.js'; -import { checkAccess } from '../utils/userUtils.js'; -import Club from '../models/Club.js'; -import DiaryDate from '../models/DiaryDates.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import PredefinedActivity from '../models/PredefinedActivity.js'; -import PredefinedActivityImage from '../models/PredefinedActivityImage.js'; -import Group from '../models/Group.js'; -import GroupActivity from '../models/GroupActivity.js'; - -const createClubAndDate = async () => { - const club = await Club.create({ name: 'Service Club' }); - const diaryDate = await DiaryDate.create({ date: '2026-01-01', clubId: club.id }); - return { club, diaryDate }; -}; - -describe('diaryDateActivityService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - it('erstellt Aktivitäten mit automatisch berechneter Reihenfolge', async () => { - const { club, diaryDate } = await createClubAndDate(); - - const first = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'Warmup', - duration: '30', - isTimeblock: false, - }); - - const second = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'Drills', - duration: '45', - isTimeblock: false, - }); - - expect(checkAccess).toHaveBeenCalledTimes(2); - expect(first.orderId).toBe(1); - expect(second.orderId).toBe(2); - const all = await DiaryDateActivity.findAll({ where: { diaryDateId: diaryDate.id } }); - expect(all).toHaveLength(2); - }); - - it('aktualisiert Aktivitäten und legt neue vordefinierte Aktivitäten an', async () => { - const { club, diaryDate } = await createClubAndDate(); - const activity = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'Blocken', - duration: '20', - isTimeblock: false, - }); - - const updated = await diaryDateActivityService.updateActivity('token', club.id, activity.id, { - customActivityName: 'Topspin', - duration: 25, - }); - - const predefined = await PredefinedActivity.findOne({ where: { name: 'Topspin' } }); - - expect(updated.predefinedActivityId).toBe(predefined.id); - expect(predefined.duration).toBe(25); - }); - - it('ändert die Reihenfolge und verschiebt Nachbarn korrekt', async () => { - const { club, diaryDate } = await createClubAndDate(); - const first = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'A', - }); - const second = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'B', - }); - const third = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'C', - }); - - await diaryDateActivityService.updateActivityOrder('token', club.id, third.id, 1); - - const reloaded = await DiaryDateActivity.findAll({ - where: { diaryDateId: diaryDate.id }, - order: [['orderId', 'ASC']], - }); - - expect(reloaded[0].id).toBe(third.id); - expect(reloaded.map((item) => item.orderId)).toEqual([1, 2, 3]); - }); - - it('liefert Aktivitäten mit Bild-Links und Gruppendaten', async () => { - const { club, diaryDate } = await createClubAndDate(); - const predefined = await PredefinedActivity.create({ name: 'Vorhand', code: 'FH' }); - const activity = await DiaryDateActivity.create({ - diaryDateId: diaryDate.id, - predefinedActivityId: predefined.id, - orderId: 1, - isTimeblock: false, - }); - await PredefinedActivityImage.create({ - predefinedActivityId: predefined.id, - drawingData: JSON.stringify({ circles: 2 }), - mimeType: 'image/png', - fileName: 'test.png', - imagePath: '/uploads/test.png', - }); - - const group = await Group.create({ diaryDateId: diaryDate.id, name: 'Gruppe 1' }); - const groupPredefined = await PredefinedActivity.create({ name: 'Rally', code: 'RL' }); - await GroupActivity.create({ - diaryDateActivity: activity.id, - groupId: group.id, - customActivity: groupPredefined.id, - }); - - const result = await diaryDateActivityService.getActivities('token', club.id, diaryDate.id); - - expect(result).toHaveLength(1); - expect(result[0].predefinedActivity.imageUrl).toContain(`/api/predefined-activities/${predefined.id}/image/`); - expect(result[0].groupActivities).toHaveLength(1); - }); - - it('fügt Gruppenaktivitäten in Zeitblöcke ein', async () => { - const { club, diaryDate } = await createClubAndDate(); - const timeblock = await DiaryDateActivity.create({ - diaryDateId: diaryDate.id, - isTimeblock: true, - orderId: 1, - }); - const group = await Group.create({ diaryDateId: diaryDate.id, name: 'Gruppe 2' }); - - const created = await diaryDateActivityService.addGroupActivity('token', club.id, diaryDate.id, group.id, 'Abschlussspiel', timeblock.id); - - expect(created.diaryDateActivity).toBe(timeblock.id); - expect(created.groupId).toBe(group.id); - }); - - it('löscht Aktivitäten und zugehörige Gruppeneinträge', async () => { - const { club, diaryDate } = await createClubAndDate(); - const activity = await diaryDateActivityService.createActivity('token', club.id, { - diaryDateId: diaryDate.id, - activity: 'Auslaufen', - }); - - await diaryDateActivityService.deleteActivity('token', club.id, activity.id); - const remaining = await DiaryDateActivity.findByPk(activity.id); - expect(remaining).toBeNull(); - }); - - it('löscht Gruppenaktivitäten', async () => { - const { club, diaryDate } = await createClubAndDate(); - const timeblock = await DiaryDateActivity.create({ - diaryDateId: diaryDate.id, - isTimeblock: true, - orderId: 1, - }); - const group = await Group.create({ diaryDateId: diaryDate.id, name: 'Gruppe 3' }); - const created = await GroupActivity.create({ - diaryDateActivity: timeblock.id, - groupId: group.id, - customActivity: null, - }); - - await diaryDateActivityService.deleteGroupActivity('token', club.id, created.id); - const exists = await GroupActivity.findByPk(created.id); - expect(exists).toBeNull(); - }); -}); diff --git a/backend/tests/diaryMemberActivityRoutes.test.js b/backend/tests/diaryMemberActivityRoutes.test.js deleted file mode 100644 index eecf4a7..0000000 --- a/backend/tests/diaryMemberActivityRoutes.test.js +++ /dev/null @@ -1,129 +0,0 @@ -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 '../models/index.js'; - -import Member from '../models/Member.js'; -import Participant from '../models/Participant.js'; -import DiaryDate from '../models/DiaryDates.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import DiaryMemberActivity from '../models/DiaryMemberActivity.js'; -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -const createMember = async (clubId, firstName, lastName, email) => { - return Member.create({ - firstName, - lastName, - phone: '0123456789', - street: 'Teststraße 1', - city: 'Teststadt', - postalCode: '12345', - email, - clubId, - birthDate: '2000-01-01', - active: true, - testMembership: false, - picsInInternetAllowed: false, - gender: 'female', - }); -}; - -describe('DiaryMemberActivity Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('fügt Teilnehmer zu Aktivitäten hinzu, listet und entfernt sie wieder', async () => { - const { credentials } = await registerAndActivate('dma@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Member Activity Club' }); - const clubId = clubResponse.body.id; - - const diaryDateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token)) - .send({ date: '2026-03-01' }); - const diaryDateId = diaryDateResponse.body.id; - - const activityResponse = await request(app) - .post(`/api/diary-date-activities/${clubId}/`) - .set(authHeaders(token)) - .send({ diaryDateId, activity: 'Koordination', isTimeblock: false }); - const activityId = activityResponse.body.id; - - const member = await createMember(clubId, 'Anna', 'Trainer', 'anna@example.com'); - const participant = await Participant.create({ diaryDateId, memberId: member.id }); - - const addResponse = await request(app) - .post(`/api/diary-member-activities/${clubId}/${activityId}`) - .set(authHeaders(token)) - .send({ participantIds: [participant.id] }); - - expect(addResponse.status).toBe(201); - expect(addResponse.body).toHaveLength(1); - - const listResponse = await request(app) - .get(`/api/diary-member-activities/${clubId}/${activityId}`) - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body[0].participantId).toBe(participant.id); - - const deleteResponse = await request(app) - .delete(`/api/diary-member-activities/${clubId}/${activityId}/${participant.id}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - const remaining = await DiaryMemberActivity.findAll({ where: { diaryDateActivityId: activityId } }); - expect(remaining).toHaveLength(0); - }); - - it('prüft ungültige Nutzlasten', async () => { - const { credentials } = await registerAndActivate('dma-invalid@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Invalid Payload Club' }); - const clubId = clubResponse.body.id; - - const diaryDate = await DiaryDate.create({ date: '2026-04-01', clubId }); - const activity = await DiaryDateActivity.create({ diaryDateId: diaryDate.id, orderId: 1, isTimeblock: false }); - - const response = await request(app) - .post(`/api/diary-member-activities/${clubId}/${activity.id}`) - .set(authHeaders(token)) - .send({ participantIds: 'nicht-array' }); - - expect(response.status).toBe(400); - }); -}); diff --git a/backend/tests/diaryNoteRoutes.test.js b/backend/tests/diaryNoteRoutes.test.js deleted file mode 100644 index 82ca384..0000000 --- a/backend/tests/diaryNoteRoutes.test.js +++ /dev/null @@ -1,106 +0,0 @@ -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 '../models/index.js'; - -import DiaryDate from '../models/DiaryDates.js'; -import DiaryNote from '../models/DiaryNote.js'; -import User from '../models/User.js'; -import { createMember } from './utils/factories.js'; -import { setupRouteAuthMocks } from './utils/routeAuthMocks.js'; - -setupRouteAuthMocks(); - -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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('DiaryNote Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt, listet und löscht Notizen', async () => { - const { credentials } = await registerAndActivate('diarynote@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Notiz Club' }); - const clubId = clubResponse.body.id; - - const diaryDateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token)) - .send({ date: '2026-05-01' }); - const diaryDateId = diaryDateResponse.body.id; - - const member = await createMember(clubId, { - firstName: 'Note', - lastName: 'Tester', - birthDate: '2000-01-01', - email: 'note.member@example.com', - gender: 'female', - }); - - const createResponse = await request(app) - .post('/api/diary-notes') - .set(authHeaders(token)) - .send({ memberId: member.id, diaryDateId, content: 'Gute Einheit' }); - - expect(createResponse.status).toBe(201); - expect(createResponse.body.content).toBe('Gute Einheit'); - - const listResponse = await request(app) - .get('/api/diary-notes') - .set(authHeaders(token)) - .query({ diaryDateId, memberId: member.id }); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - expect(listResponse.body[0].content).toBe('Gute Einheit'); - - const noteId = createResponse.body.id; - - const deleteResponse = await request(app) - .delete(`/api/diary-notes/${noteId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - const remaining = await DiaryNote.findByPk(noteId); - expect(remaining).toBeNull(); - }); - - it('validiert Pflichtfelder beim Erstellen', async () => { - const { credentials } = await registerAndActivate('diarynote-invalid@example.com'); - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .post('/api/diary-notes') - .set(authHeaders(token)) - .send({ content: 'Fehlende Felder' }); - - expect(response.status).toBe(400); - }); -}); diff --git a/backend/tests/diaryRoutes.test.js b/backend/tests/diaryRoutes.test.js deleted file mode 100644 index e9aff3e..0000000 --- a/backend/tests/diaryRoutes.test.js +++ /dev/null @@ -1,164 +0,0 @@ -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 '../models/index.js'; -import { setupRouteAuthMocks } from './utils/routeAuthMocks.js'; - -setupRouteAuthMocks(); - -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import DiaryDate from '../models/DiaryDates.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import DiaryTag from '../models/DiaryTag.js'; -import DiaryDateTag from '../models/DiaryDateTag.js'; -import DiaryNote from '../models/DiaryNote.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; -}; - -const authHeaders = (token, clubId) => ({ - Authorization: `Bearer ${token}`, - authcode: token, - clubid: clubId, -}); - -describe('Diary Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt Diary-Daten und listet sie auf', async () => { - const { credentials } = await registerAndActivate('diary@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token, 'clubId')) - .send({ name: 'Diary Club' }); - const clubId = clubResponse.body.id; - - const createResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token, clubId)) - .send({ date: '2025-09-01', trainingStart: '18:00', trainingEnd: '20:00' }); - - expect(createResponse.status).toBe(201); - - const listResponse = await request(app) - .get(`/api/diary/${clubId}`) - .set(authHeaders(token, clubId)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - expect(listResponse.body[0].trainingStart).toBe('18:00'); - }); - - it('aktualisiert Trainingszeiten eines Diary-Eintrags', async () => { - const { credentials } = await registerAndActivate('diary2@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token, 'clubId')) - .send({ name: 'Update Diary Club' }); - const clubId = clubResponse.body.id; - - const dateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token, clubId)) - .send({ date: '2025-10-01' }); - const dateId = dateResponse.body.id; - - const updateResponse = await request(app) - .put(`/api/diary/${clubId}`) - .set(authHeaders(token, clubId)) - .send({ dateId, trainingStart: '17:00', trainingEnd: '19:00' }); - - expect(updateResponse.status).toBe(200); - expect(updateResponse.body.trainingStart).toBe('17:00'); - }); - - it('verhindert das Löschen bei vorhandenen Aktivitäten', async () => { - const { credentials } = await registerAndActivate('diary3@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token, 'clubId')) - .send({ name: 'Activity Diary Club' }); - const clubId = clubResponse.body.id; - - const dateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token, clubId)) - .send({ date: '2025-11-01' }); - const dateId = dateResponse.body.id; - - await DiaryDateActivity.create({ diaryDateId: dateId, orderId: 1, isTimeblock: false }); - - const deleteResponse = await request(app) - .delete(`/api/diary/${clubId}/${dateId}`) - .set(authHeaders(token, clubId)); - - expect(deleteResponse.status).toBe(409); - expect(deleteResponse.body.error).toBe('Cannot delete date with activities'); - }); - - it('verwaltet Tags über die Diary-API', async () => { - const { credentials } = await registerAndActivate('diary4@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token, 'clubId')) - .send({ name: 'Tag Diary Club' }); - const clubId = clubResponse.body.id; - - const dateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token, clubId)) - .send({ date: '2025-12-01' }); - const dateId = dateResponse.body.id; - - const createTagResponse = await request(app) - .post('/api/diary/tag') - .set(authHeaders(token, clubId)) - .send({ clubId, diaryDateId: dateId, tagName: 'Ausdauer' }); - - expect(createTagResponse.status).toBe(201); - const tagId = createTagResponse.body[0].id; - - const linkResponse = await request(app) - .post(`/api/diary/tag/${clubId}/add-tag`) - .set(authHeaders(token, clubId)) - .send({ diaryDateId: dateId, tagId }); - - expect(linkResponse.status).toBe(200); - - const deleteResponse = await request(app) - .delete(`/api/diary/${clubId}/tag`) - .set(authHeaders(token, clubId)) - .query({ tagId }); - - expect(deleteResponse.status).toBe(200); - const remaining = await DiaryDateTag.findAll({ where: { diaryDateId: dateId } }); - expect(remaining).toHaveLength(0); - }); -}); diff --git a/backend/tests/diaryService.test.js b/backend/tests/diaryService.test.js deleted file mode 100644 index 21319d9..0000000 --- a/backend/tests/diaryService.test.js +++ /dev/null @@ -1,134 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import diaryService from '../services/diaryService.js'; -import Club from '../models/Club.js'; -import DiaryDate from '../models/DiaryDates.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import DiaryDateTag from '../models/DiaryDateTag.js'; -import { DiaryTag } from '../models/DiaryTag.js'; -import { checkAccess } from '../utils/userUtils.js'; - -describe('diaryService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - it('liefert alle Diary-Daten eines Clubs', async () => { - const club = await Club.create({ name: 'Diary Club' }); - await DiaryDate.bulkCreate([ - { date: '2025-01-01', clubId: club.id, trainingStart: '18:00', trainingEnd: '20:00' }, - { date: '2025-01-05', clubId: club.id, trainingStart: '19:00', trainingEnd: '21:00' }, - ]); - - const dates = await diaryService.getDatesForClub('token', club.id); - - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - expect(dates).toHaveLength(2); - expect(dates[0].clubId).toBe(club.id); - }); - - it('erstellt neue Diary-Daten und validiert Zeiten', async () => { - const club = await Club.create({ name: 'Create Club' }); - - const created = await diaryService.createDateForClub('token', club.id, '2025-02-02', '18:00', '20:00'); - - expect(created.id).toBeTruthy(); - expect(created.trainingStart).toBe('18:00'); - - await expect( - diaryService.createDateForClub('token', club.id, '2025-02-03', '20:00', '19:00') - ).rejects.toThrow('Training start time must be before training end time'); - - await expect( - diaryService.createDateForClub('token', club.id, 'ungültig', '20:00', '21:00') - ).rejects.toThrow('Invalid date format'); - }); - - it('aktualisiert Trainingszeiten und wirft Fehler bei fehlendem Eintrag', async () => { - const club = await Club.create({ name: 'Update Club' }); - const date = await DiaryDate.create({ date: '2025-03-01', clubId: club.id }); - - const updated = await diaryService.updateTrainingTimes('token', club.id, date.id, '17:00', '19:00'); - expect(updated.trainingStart).toBe('17:00'); - - await expect( - diaryService.updateTrainingTimes('token', club.id, 9999, '10:00', '12:00') - ).rejects.toThrow('Diary entry not found'); - }); - - it('fügt Tags über Namen hinzu und liefert zugehörige Tag-Liste', async () => { - const club = await Club.create({ name: 'Tag Club' }); - const date = await DiaryDate.create({ date: '2025-04-01', clubId: club.id }); - - const tags = await diaryService.addTagToDate('token', date.id, 'Intensiv'); - - expect(checkAccess).toHaveBeenCalledWith('token', date.id); - expect(tags.length).toBe(1); - expect(tags[0].name).toBe('Intensiv'); - }); - - it('verknüpft bestehende Tags mit Diary-Daten', async () => { - const club = await Club.create({ name: 'Tag Link Club' }); - const date = await DiaryDate.create({ date: '2025-05-01', clubId: club.id }); - const tag = await DiaryTag.create({ name: 'Technik' }); - - const result = await diaryService.addTagToDiaryDate('token', club.id, date.id, tag.id); - - expect(result).toHaveLength(1); - expect(result[0].name).toBe('Technik'); - - const second = await diaryService.addTagToDiaryDate('token', club.id, date.id, tag.id); - expect(second).toBeUndefined(); - }); - - it('entfernt Tags von Diary-Daten', async () => { - const club = await Club.create({ name: 'Remove Tag Club' }); - const date = await DiaryDate.create({ date: '2025-06-01', clubId: club.id }); - const tag = await DiaryTag.create({ name: 'Kondition' }); - await DiaryDateTag.create({ diaryDateId: date.id, tagId: tag.id }); - - await diaryService.removeTagFromDiaryDate('token', club.id, tag.id); - - const remaining = await DiaryDateTag.findAll({ where: { diaryDateId: date.id } }); - expect(remaining).toHaveLength(0); - }); - - it('verhindert das Löschen bei vorhandenen Aktivitäten', async () => { - const club = await Club.create({ name: 'Activity Club' }); - const date = await DiaryDate.create({ date: '2025-07-01', clubId: club.id }); - await DiaryDateActivity.create({ diaryDateId: date.id, orderId: 1, isTimeblock: false }); - - await expect( - diaryService.removeDateForClub('token', club.id, date.id) - ).rejects.toThrow('Cannot delete date with activities'); - }); - - it('löscht Diary-Daten ohne Aktivitäten', async () => { - const club = await Club.create({ name: 'Delete Diary Club' }); - const date = await DiaryDate.create({ date: '2025-08-01', clubId: club.id }); - - const result = await diaryService.removeDateForClub('token', club.id, date.id); - - expect(result).toEqual({ ok: true }); - const exists = await DiaryDate.findByPk(date.id); - expect(exists).toBeNull(); - }); - - it('meldet Fehler bei fehlenden Clubs oder Einträgen', async () => { - await expect(diaryService.getDatesForClub('token', 9999)).rejects.toThrow('Club not found'); - const club = await Club.create({ name: '404 Club' }); - await expect(diaryService.removeDateForClub('token', club.id, 9999)).rejects.toThrow('Diary entry not found'); - }); -}); diff --git a/backend/tests/diaryTagRoutes.test.js b/backend/tests/diaryTagRoutes.test.js deleted file mode 100644 index 34b96cb..0000000 --- a/backend/tests/diaryTagRoutes.test.js +++ /dev/null @@ -1,80 +0,0 @@ -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 '../models/index.js'; - -import { DiaryTag } from '../models/DiaryTag.js'; -import DiaryDateTag from '../models/DiaryDateTag.js'; -import DiaryDate from '../models/DiaryDates.js'; -import Club from '../models/Club.js'; -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('DiaryTag Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt und listet Tags', async () => { - const { credentials } = await registerAndActivate('diarytag@example.com'); - const token = await loginAndGetToken(credentials); - - const createResponse = await request(app) - .post('/api/diary-tags') - .set(authHeaders(token)) - .send({ name: 'Technik' }); - - expect(createResponse.status).toBe(201); - expect(createResponse.body.name).toBe('Technik'); - - const listResponse = await request(app) - .get('/api/diary-tags') - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - }); - - it('löscht Tags inklusive Zuordnungen', async () => { - const { credentials } = await registerAndActivate('diarytag-delete@example.com'); - const token = await loginAndGetToken(credentials); - - const club = await Club.create({ name: 'Tag Club' }); - const diaryDate = await DiaryDate.create({ date: '2026-06-01', clubId: club.id }); - const tag = await DiaryTag.create({ name: 'Ausdauer' }); - await DiaryDateTag.create({ diaryDateId: diaryDate.id, tagId: tag.id }); - - const deleteResponse = await request(app) - .delete(`/api/diary-tags/${tag.id}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - const tagExists = await DiaryTag.findByPk(tag.id); - expect(tagExists).toBeNull(); - const relations = await DiaryDateTag.findAll({ where: { tagId: tag.id } }); - expect(relations).toHaveLength(0); - }); -}); diff --git a/backend/tests/groupRoutes.test.js b/backend/tests/groupRoutes.test.js deleted file mode 100644 index 04d658e..0000000 --- a/backend/tests/groupRoutes.test.js +++ /dev/null @@ -1,78 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Group Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt, listet und aktualisiert Gruppen', async () => { - const { credentials } = await registerAndActivate('groups@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Group Route Club' }); - const clubId = clubResponse.body.id; - - const diaryDateResponse = await request(app) - .post(`/api/diary/${clubId}`) - .set(authHeaders(token)) - .send({ date: '2026-08-01' }); - const diaryDateId = diaryDateResponse.body.id; - - const createResponse = await request(app) - .post('/api/groups') - .set(authHeaders(token)) - .send({ clubid: clubId, dateid: diaryDateId, name: 'Gruppe 1', lead: 'Coach' }); - - expect(createResponse.status).toBe(201); - const groupId = createResponse.body.id; - - const listResponse = await request(app) - .get(`/api/groups/${clubId}/${diaryDateId}`) - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - expect(listResponse.body[0].name).toBe('Gruppe 1'); - - const updateResponse = await request(app) - .put(`/api/groups/${groupId}`) - .set(authHeaders(token)) - .send({ clubid: clubId, dateid: diaryDateId, name: 'Gruppe 1', lead: 'Neue Leitung' }); - - expect(updateResponse.status).toBe(200); - expect(updateResponse.body.lead).toBe('Neue Leitung'); - }); -}); diff --git a/backend/tests/groupService.test.js b/backend/tests/groupService.test.js deleted file mode 100644 index 19ef650..0000000 --- a/backend/tests/groupService.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import groupService from '../services/groupService.js'; -import { checkAccess } from '../utils/userUtils.js'; -import Club from '../models/Club.js'; -import DiaryDate from '../models/DiaryDates.js'; -import Group from '../models/Group.js'; - -describe('groupService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - const setupClubAndDate = async () => { - const club = await Club.create({ name: 'Group Club' }); - const diaryDate = await DiaryDate.create({ date: '2026-07-01', clubId: club.id }); - return { club, diaryDate }; - }; - - it('legt Gruppen an und gibt sie zurück', async () => { - const { club, diaryDate } = await setupClubAndDate(); - - const group = await groupService.addGroup('token', club.id, diaryDate.id, 'Team Blau', 'Coach'); - - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - expect(group.name).toBe('Team Blau'); - - const groups = await groupService.getGroups('token', club.id, diaryDate.id); - expect(groups).toHaveLength(1); - expect(groups[0].lead).toBe('Coach'); - }); - - it('aktualisiert Gruppenangaben und validiert Zugehörigkeit', async () => { - const { club, diaryDate } = await setupClubAndDate(); - const group = await groupService.addGroup('token', club.id, diaryDate.id, 'Team Rot', 'Trainerin'); - - const updated = await groupService.changeGroup('token', group.id, club.id, diaryDate.id, 'Team Rot', 'Neuer Lead'); - - expect(updated.lead).toBe('Neuer Lead'); - - await expect( - groupService.changeGroup('token', group.id, club.id, diaryDate.id + 1, 'Fail', 'Lead') - ).rejects.toThrow('Datum nicht gefunden oder passt nicht zum Verein'); - }); -}); diff --git a/backend/tests/matchRoutes.test.js b/backend/tests/matchRoutes.test.js deleted file mode 100644 index 38dfe8f..0000000 --- a/backend/tests/matchRoutes.test.js +++ /dev/null @@ -1,85 +0,0 @@ -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 '../models/index.js'; - -import Season from '../models/Season.js'; -import League from '../models/League.js'; -import Team from '../models/Team.js'; -import Match from '../models/Match.js'; -import Club from '../models/Club.js'; -import Location from '../models/Location.js'; -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Match Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('liefert Matches und aktualisiert Spielerlisten', async () => { - const { credentials } = await registerAndActivate('matchroutes@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Match Route Club' }); - const clubId = clubResponse.body.id; - - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'Route Liga', clubId, seasonId: season.id }); - const homeTeam = await Team.create({ name: 'Route Club I', clubId, leagueId: league.id, seasonId: season.id }); - const guestTeam = await Team.create({ name: 'Route Club II', clubId, leagueId: league.id, seasonId: season.id }); - const location = await Location.create({ name: 'Route Halle', address: 'Straße 1', city: 'Stadt', zip: '12345' }); - const match = await Match.create({ - clubId, - leagueId: league.id, - homeTeamId: homeTeam.id, - guestTeamId: guestTeam.id, - locationId: location.id, - date: new Date('2025-09-01T18:00:00Z'), - time: '18:00', - }); - - const listResponse = await request(app) - .get(`/api/matches/leagues/${clubId}/matches`) - .set(authHeaders(token)) - .query({ seasonid: season.id }); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - expect(listResponse.body[0].homeTeam.name).toBe('Route Club I'); - - const patchResponse = await request(app) - .patch(`/api/matches/${match.id}/players`) - .set(authHeaders(token)) - .send({ clubId, playersReady: ['Alice'], playersPlanned: ['Bob'], playersPlayed: ['Charlie'] }); - - expect(patchResponse.status).toBe(200); - expect(patchResponse.body.data.playersReady).toEqual(['Alice']); - }); -}); diff --git a/backend/tests/matchService.test.js b/backend/tests/matchService.test.js deleted file mode 100644 index 6e5b144..0000000 --- a/backend/tests/matchService.test.js +++ /dev/null @@ -1,83 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import matchService from '../services/matchService.js'; -import { checkAccess } from '../utils/userUtils.js'; -import Season from '../models/Season.js'; -import League from '../models/League.js'; -import Team from '../models/Team.js'; -import Match from '../models/Match.js'; -import Club from '../models/Club.js'; -import Location from '../models/Location.js'; - -describe('matchService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - it('formatiert Team-Namen anhand der Altersklasse', () => { - expect(matchService.formatTeamNameWithAgeClass('Harheimer TC', 'Jugend 11')).toBe('Harheimer TC (J11)'); - expect(matchService.formatTeamNameWithAgeClass('Harheimer TC', 'Senioren')).toBe('Harheimer TC (S)'); - expect(matchService.formatTeamNameWithAgeClass('Harheimer TC', 'Erwachsene')).toBe('Harheimer TC'); - }); - - it('erstellt Teams und gibt bestehende Einträge zurück', async () => { - const club = await Club.create({ name: 'Match Club' }); - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'Verbandsliga', clubId: club.id, seasonId: season.id }); - - const teamId = await matchService.getOrCreateTeamId('Harheimer TC', 'Jugend 11', club.id, league.id, season.id); - const sameTeamId = await matchService.getOrCreateTeamId('Harheimer TC', 'Jugend 11', club.id, league.id, season.id); - - expect(teamId).toBeTruthy(); - expect(teamId).toBe(sameTeamId); - const team = await Team.findByPk(teamId); - expect(team.name).toBe('Harheimer TC (J11)'); - }); - - it('liefert Ligen und Matches für eine Saison und aktualisiert Spielerlisten', async () => { - const club = await Club.create({ name: 'Season Club' }); - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'Oberliga', clubId: club.id, seasonId: season.id }); - const homeTeam = await Team.create({ name: 'Season Club I', clubId: club.id, leagueId: league.id, seasonId: season.id }); - const guestTeam = await Team.create({ name: 'Season Club II', clubId: club.id, leagueId: league.id, seasonId: season.id }); - const location = await Location.create({ name: 'Halle A', address: 'Straße 1', city: 'Stadt', zip: '12345' }); - - const match = await Match.create({ - clubId: club.id, - leagueId: league.id, - homeTeamId: homeTeam.id, - guestTeamId: guestTeam.id, - locationId: location.id, - date: new Date('2025-09-01T18:00:00Z'), - time: '18:00', - }); - - const leagues = await matchService.getLeaguesForCurrentSeason('token', club.id, season.id); - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - expect(leagues).toHaveLength(1); - expect(leagues[0].name).toBe('Oberliga'); - - const matches = await matchService.getMatchesForLeagues('token', club.id, season.id); - expect(matches).toHaveLength(1); - expect(matches[0].homeTeam.name).toBe('Season Club I'); - expect(matches[0].location.name).toBe('Halle A'); - - await matchService.updateMatchPlayers('token', match.id, ['Alice'], ['Bob'], ['Charlie']); - const updated = await Match.findByPk(match.id); - expect(updated.playersReady).toEqual(['Alice']); - expect(updated.playersPlanned).toEqual(['Bob']); - expect(updated.playersPlayed).toEqual(['Charlie']); - }); -}); diff --git a/backend/tests/memberActivityRoutes.test.js b/backend/tests/memberActivityRoutes.test.js deleted file mode 100644 index dc266f4..0000000 --- a/backend/tests/memberActivityRoutes.test.js +++ /dev/null @@ -1,127 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import DiaryDate from '../models/DiaryDates.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import DiaryMemberActivity from '../models/DiaryMemberActivity.js'; -import Participant from '../models/Participant.js'; -import PredefinedActivity from '../models/PredefinedActivity.js'; -import Group from '../models/Group.js'; -import GroupActivity from '../models/GroupActivity.js'; -import { createMember } from './utils/factories.js'; -import { setupRouteAuthMocks } from './utils/routeAuthMocks.js'; - -setupRouteAuthMocks(); - -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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('MemberActivity Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('aggregiert Aktivitäten und liefert letzte Teilnahmen', async () => { - const { credentials } = await registerAndActivate('memberactivity@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Activity Club' }); - const clubId = clubResponse.body.id; - - const member = await createMember(clubId, { - firstName: 'Max', - lastName: 'Mustermann', - birthDate: '2005-01-01', - email: 'max@example.com', - testMembership: true, - gender: 'male', - }); - - const diaryDateRecent = await DiaryDate.create({ date: '2026-09-01', clubId }); - const diaryDateOlder = await DiaryDate.create({ date: '2026-06-01', clubId }); - const group = await Group.create({ diaryDateId: diaryDateRecent.id, name: 'Gruppe A' }); - const otherGroup = await Group.create({ diaryDateId: diaryDateOlder.id, name: 'Gruppe B' }); - - const participant = await Participant.create({ diaryDateId: diaryDateRecent.id, memberId: member.id, groupId: group.id }); - - const activityGeneralDef = await PredefinedActivity.create({ name: 'Allgemeines Training' }); - const activityGroupDef = await PredefinedActivity.create({ name: 'Gruppenübung' }); - const activityOtherDef = await PredefinedActivity.create({ name: 'Falsche Gruppe' }); - - const generalActivity = await DiaryDateActivity.create({ - diaryDateId: diaryDateOlder.id, - predefinedActivityId: activityGeneralDef.id, - orderId: 1, - isTimeblock: false, - }); - - const groupActivity = await DiaryDateActivity.create({ - diaryDateId: diaryDateRecent.id, - predefinedActivityId: activityGroupDef.id, - orderId: 2, - isTimeblock: false, - }); - - await GroupActivity.create({ diaryDateActivity: groupActivity.id, groupId: group.id, customActivity: null }); - - const otherActivity = await DiaryDateActivity.create({ - diaryDateId: diaryDateOlder.id, - predefinedActivityId: activityOtherDef.id, - orderId: 1, - isTimeblock: false, - }); - await GroupActivity.create({ diaryDateActivity: otherActivity.id, groupId: otherGroup.id, customActivity: null }); - - await DiaryMemberActivity.bulkCreate([ - { diaryDateActivityId: generalActivity.id, participantId: participant.id }, - { diaryDateActivityId: groupActivity.id, participantId: participant.id }, - { diaryDateActivityId: otherActivity.id, participantId: participant.id }, - ]); - - const activitiesResponse = await request(app) - .get(`/api/member-activities/${clubId}/${member.id}`) - .set(authHeaders(token)) - .query({ period: 'year' }); - - expect(activitiesResponse.status).toBe(200); - const names = activitiesResponse.body.map((entry) => entry.name).sort(); - expect(names).toEqual(['Allgemeines Training', 'Gruppenübung']); - - const lastResponse = await request(app) - .get(`/api/member-activities/${clubId}/${member.id}/last-participations`) - .set(authHeaders(token)) - .query({ limit: 1 }); - - expect(lastResponse.status).toBe(200); - expect(lastResponse.body).toHaveLength(1); - expect(lastResponse.body[0].activityName).toBe('Gruppenübung'); - }); -}); diff --git a/backend/tests/memberNoteRoutes.test.js b/backend/tests/memberNoteRoutes.test.js deleted file mode 100644 index 9524d37..0000000 --- a/backend/tests/memberNoteRoutes.test.js +++ /dev/null @@ -1,80 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import request from 'supertest'; - -import app from './testApp.js'; -import sequelize from '../database.js'; -import '../models/index.js'; - -import User from '../models/User.js'; -import { createMember } from './utils/factories.js'; -import { setupRouteAuthMocks } from './utils/routeAuthMocks.js'; - -setupRouteAuthMocks(); - -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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('MemberNote Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt, listet und löscht Member Notes', async () => { - const { credentials } = await registerAndActivate('membernote@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Note Club' }); - const clubId = clubResponse.body.id; - - const member = await createMember(clubId, { - firstName: 'Nora', - lastName: 'Notiz', - birthDate: '1999-01-01', - email: 'nora@example.com', - gender: 'female', - }); - - const createResponse = await request(app) - .post('/api/member-notes') - .set(authHeaders(token)) - .send({ memberId: member.id, clubId, content: 'Erste Notiz' }); - - expect(createResponse.status).toBe(201); - expect(createResponse.body[0].content).toBe('Erste Notiz'); - - const listResponse = await request(app) - .get(`/api/member-notes/${member.id}`) - .set(authHeaders(token)) - .query({ clubId }); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - - const noteId = createResponse.body[0].id; - const deleteResponse = await request(app) - .delete(`/api/member-notes/${noteId}`) - .set(authHeaders(token)) - .send({ clubId }); - - expect(deleteResponse.status).toBe(200); - expect(deleteResponse.body).toHaveLength(0); - }); -}); diff --git a/backend/tests/memberRoutes.test.js b/backend/tests/memberRoutes.test.js deleted file mode 100644 index 9e7d1b2..0000000 --- a/backend/tests/memberRoutes.test.js +++ /dev/null @@ -1,103 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.js'; -import { createMember } from './utils/factories.js'; -import { setupRouteAuthMocks } from './utils/routeAuthMocks.js'; - -setupRouteAuthMocks(); - -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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Member quick action routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('setzt Testmitgliedschaft, Formularstatus und deaktiviert Mitglieder', async () => { - const { credentials } = await registerAndActivate('memberroutes@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Member Route Club' }); - const clubId = clubResponse.body.id; - - const member = await createMember(clubId, { - firstName: 'Lara', - lastName: 'Lang', - birthDate: '2001-01-01', - testMembership: true, - memberFormHandedOver: false, - active: true, - }); - - const testResponse = await request(app) - .post(`/api/members/quick-update-test-membership/${clubId}/${member.id}`) - .set(authHeaders(token)); - - expect(testResponse.status).toBe(200); - await member.reload(); - expect(member.testMembership).toBe(false); - - const formResponse = await request(app) - .post(`/api/members/quick-update-member-form/${clubId}/${member.id}`) - .set(authHeaders(token)); - - expect(formResponse.status).toBe(200); - await member.reload(); - expect(member.memberFormHandedOver).toBe(true); - - const deactivateResponse = await request(app) - .post(`/api/members/quick-deactivate/${clubId}/${member.id}`) - .set(authHeaders(token)); - - expect(deactivateResponse.status).toBe(200); - await member.reload(); - expect(member.active).toBe(false); - }); - - it('validiert Transfer-Konfiguration über die API', async () => { - const { credentials } = await registerAndActivate('membertransfer@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Transfer Test Club' }); - const clubId = clubResponse.body.id; - - const transferResponse = await request(app) - .post(`/api/members/transfer/${clubId}`) - .set(authHeaders(token)) - .send({ transferTemplate: '{}' }); - - expect(transferResponse.status).toBe(400); - expect(transferResponse.body.error).toContain('Übertragungs-Endpoint'); - }); -}); diff --git a/backend/tests/memberService.test.js b/backend/tests/memberService.test.js deleted file mode 100644 index 2b99163..0000000 --- a/backend/tests/memberService.test.js +++ /dev/null @@ -1,110 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import memberService from '../services/memberService.js'; -import { checkAccess } from '../utils/userUtils.js'; -import Member from '../models/Member.js'; -import MemberContact from '../models/MemberContact.js'; -import Club from '../models/Club.js'; -import { createMember, createClub } from './utils/factories.js'; - -describe('memberService quick updates', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - it('entfernt Testmitgliedschaften und behandelt Fehlerfälle', async () => { - const club = await createClub({ name: 'Quick Update Club' }); - const member = await createMember(club.id, { - firstName: 'Erika', - lastName: 'Musterfrau', - birthDate: '2000-01-01', - testMembership: true, - }); - - const result = await memberService.quickUpdateTestMembership('token', member.clubId, member.id); - - expect(checkAccess).toHaveBeenCalledWith('token', member.clubId); - expect(result.status).toBe(200); - await member.reload(); - expect(member.testMembership).toBe(false); - - const alreadyLive = await memberService.quickUpdateTestMembership('token', member.clubId, member.id); - expect(alreadyLive.status).toBe(400); - }); - - it('markiert Formular-Status und deaktiviert Mitglieder', async () => { - const club = await createClub({ name: 'Form Club' }); - const member = await createMember(club.id, { - firstName: 'Erika', - lastName: 'Musterfrau', - birthDate: '2000-01-01', - testMembership: false, - memberFormHandedOver: false, - }); - - const formResult = await memberService.quickUpdateMemberFormHandedOver('token', member.clubId, member.id); - expect(formResult.status).toBe(200); - await member.reload(); - expect(member.memberFormHandedOver).toBe(true); - - const deactivateResult = await memberService.quickDeactivateMember('token', member.clubId, member.id); - expect(deactivateResult.status).toBe(200); - await member.reload(); - expect(member.active).toBe(false); - }); - - it('meldet 404 für fehlende Mitglieder', async () => { - const result = await memberService.quickDeactivateMember('token', 99, 123); - expect(result.status).toBe(404); - }); - - it('legt Mitglieder mit Kontakten an und aktualisiert sie', async () => { - const club = await Club.create({ name: 'Kontakt Club' }); - - const createResult = await memberService.setClubMember('token', club.id, null, 'Lena', 'Lang', 'Straße 1', 'Stadt', '12345', '2002-03-04', '+49 151 000000', 'lena@example.com', true, false, false, 'female', null, null, false, [ - { type: 'phone', value: '+49 170 123456', isPrimary: true }, - { type: 'email', value: 'contact@example.com', isParent: true, parentName: 'Mutter' }, - ]); - - expect(createResult.status).toBe(200); - const created = await Member.findOne({ where: { clubId: club.id }, include: { model: MemberContact, as: 'contacts' } }); - expect(created).toBeTruthy(); - expect(created.email).toBe('lena@example.com'); - expect(created.contacts).toHaveLength(2); - - const updateResult = await memberService.setClubMember('token', club.id, created.id, 'Lena', 'Lang', 'Neue Straße', 'Neue Stadt', '54321', '2002-03-04', '+49 89 123', 'lena@example.com', true, false, false, 'female', null, null, true, [ - { type: 'phone', value: '+49 89 999', isPrimary: true }, - ]); - - expect(updateResult.status).toBe(200); - await created.reload({ include: { model: MemberContact, as: 'contacts' } }); - expect(created.street).toBe('Neue Straße'); - expect(created.memberFormHandedOver).toBe(true); - expect(created.contacts).toHaveLength(1); - }); - - it('filtert inaktive Mitglieder standardmäßig heraus', async () => { - const club = await Club.create({ name: 'Filter Club' }); - await createMember(club.id, { active: true, email: 'active@example.com' }); - await createMember(club.id, { active: false, email: 'inactive@example.com' }); - - const onlyActive = await memberService.getClubMembers('token', club.id, 'false'); - expect(onlyActive.some((m) => m.email === 'active@example.com')).toBe(true); - expect(onlyActive.some((m) => m.email === 'inactive@example.com')).toBe(false); - - const allMembers = await memberService.getClubMembers('token', club.id, 'true'); - expect(allMembers.some((m) => m.email === 'inactive@example.com')).toBe(true); - }); -}); diff --git a/backend/tests/memberTransferConfigRoutes.test.js b/backend/tests/memberTransferConfigRoutes.test.js deleted file mode 100644 index 4520f5c..0000000 --- a/backend/tests/memberTransferConfigRoutes.test.js +++ /dev/null @@ -1,77 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.js'; -import MemberTransferConfig from '../models/MemberTransferConfig.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('MemberTransferConfig Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('verwaltet die Transfer-Konfiguration eines Vereins', async () => { - const { credentials } = await registerAndActivate('configroutes@example.com'); - const token = await loginAndGetToken(credentials); - - const clubResponse = await request(app) - .post('/api/clubs') - .set(authHeaders(token)) - .send({ name: 'Config Route Club' }); - const clubId = clubResponse.body.id; - - const saveResponse = await request(app) - .post(`/api/member-transfer-config/${clubId}`) - .set(authHeaders(token)) - .send({ - server: 'https://api.example.com', - loginEndpoint: '/login', - loginFormat: 'json', - loginCredentials: { username: 'user', password: 'secret' }, - transferEndpoint: '/transfer', - transferTemplate: '{"name":"{{fullName}}"}' - }); - - expect(saveResponse.status).toBe(200); - const stored = await MemberTransferConfig.findOne({ where: { clubId } }); - expect(stored).toBeTruthy(); - - const getResponse = await request(app) - .get(`/api/member-transfer-config/${clubId}`) - .set(authHeaders(token)); - - expect(getResponse.status).toBe(200); - expect(getResponse.body.config.loginCredentials.username).toBe('user'); - - const deleteResponse = await request(app) - .delete(`/api/member-transfer-config/${clubId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - }); -}); diff --git a/backend/tests/memberTransferConfigService.test.js b/backend/tests/memberTransferConfigService.test.js deleted file mode 100644 index cb1a66b..0000000 --- a/backend/tests/memberTransferConfigService.test.js +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import memberTransferConfigService from '../services/memberTransferConfigService.js'; -import { checkAccess } from '../utils/userUtils.js'; -import MemberTransferConfig from '../models/MemberTransferConfig.js'; -import Club from '../models/Club.js'; - -describe('memberTransferConfigService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - }); - - it('liefert 404 wenn keine Konfiguration existiert', async () => { - const club = await Club.create({ name: 'Config Club' }); - const result = await memberTransferConfigService.getConfig('token', club.id); - - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - expect(result.status).toBe(404); - }); - - it('speichert, lädt und löscht Konfigurationen mit Login-Daten', async () => { - const club = await Club.create({ name: 'Config Club' }); - - const saveResult = await memberTransferConfigService.saveConfig('token', club.id, { - server: 'https://api.example.com', - loginEndpoint: '/login', - loginFormat: 'json', - loginCredentials: { username: 'user', password: 'secret' }, - transferEndpoint: '/transfer', - transferMethod: 'POST', - transferFormat: 'json', - transferTemplate: '{"name":"{{fullName}}"}', - useBulkMode: true, - bulkWrapperTemplate: '{"members":{{members}}}' - }); - - expect(saveResult.status).toBe(200); - - const getResult = await memberTransferConfigService.getConfig('token', club.id); - expect(getResult.status).toBe(200); - expect(getResult.response.config.loginCredentials.username).toBe('user'); - - const configInDb = await MemberTransferConfig.findOne({ where: { clubId: club.id } }); - expect(configInDb).toBeTruthy(); - expect(typeof configInDb.encryptedLoginCredentials).toBe('string'); - - const deleteResult = await memberTransferConfigService.deleteConfig('token', club.id); - expect(deleteResult.status).toBe(200); - }); -}); diff --git a/backend/tests/memberTransferService.test.js b/backend/tests/memberTransferService.test.js deleted file mode 100644 index b343ac7..0000000 --- a/backend/tests/memberTransferService.test.js +++ /dev/null @@ -1,101 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('axios', () => { - return { - __esModule: true, - default: vi.fn(), - }; -}); - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import axios from 'axios'; -import sequelize from '../database.js'; -import '../models/index.js'; - -import memberTransferService from '../services/memberTransferService.js'; -import { checkAccess } from '../utils/userUtils.js'; -import Club from '../models/Club.js'; -import Member from '../models/Member.js'; -import { buildMemberData, createMember } from './utils/factories.js'; - -const axiosMock = /** @type {vi.Mock} */ (axios); - -describe('memberTransferService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - axiosMock.mockReset(); - axiosMock.mockResolvedValue({ status: 200, data: { success: true } }); - vi.clearAllMocks(); - }); - - it('filtert ungültige Mitglieder heraus und überträgt gültige', async () => { - const club = await Club.create({ name: 'Transfer Club' }); - await Member.bulkCreate([ - buildMemberData(club.id, { - firstName: 'Anna', - lastName: 'Aktiv', - birthDate: '2001-02-03', - email: 'anna@example.com', - }), - buildMemberData(club.id, { - firstName: '', - lastName: 'OhneVorname', - birthDate: null, - email: 'invalid@example.com', - }), - ]); - - const result = await memberTransferService.transferMembers('token', club.id, { - transferEndpoint: 'http://example.com/transfer', - transferTemplate: '{"firstName":"{{firstName}}","birth":"{{birthDate}}"}', - transferMethod: 'POST', - transferFormat: 'json', - useBulkMode: false, - }); - - expect(checkAccess).toHaveBeenCalledWith('token', club.id); - expect(result.status).toBe(200); - expect(result.response.transferred).toBe(1); - expect(result.response.invalidCount).toBe(1); - expect(axiosMock).toHaveBeenCalledTimes(1); - const callConfig = axiosMock.mock.calls[0][0]; - expect(callConfig.url).toBe('http://example.com/transfer'); - }); - - it('nutzt Bulk-Modus und Wrapper-Template', async () => { - const club = await Club.create({ name: 'Bulk Club' }); - await createMember(club.id, { - firstName: 'Ben', - lastName: 'Bulk', - birthDate: '1999-05-06', - email: 'ben@example.com', - }); - - axiosMock.mockResolvedValue({ status: 200, data: { success: true, summary: { imported: 1 } } }); - - const result = await memberTransferService.transferMembers('token', club.id, { - transferEndpoint: 'http://example.com/bulk', - transferTemplate: '{"name":"{{fullName}}"}', - transferMethod: 'POST', - transferFormat: 'json', - useBulkMode: true, - bulkWrapperTemplate: '{"payload":{"members":{{members}}}}', - }); - - expect(result.status).toBe(200); - const callConfig = axiosMock.mock.calls[0][0]; - expect(callConfig.data).toEqual({ payload: { members: [{ name: 'Ben Bulk' }] } }); - }); - - it('formatiert Geburtsdaten korrekt', () => { - expect(memberTransferService.formatBirthDate('01.02.2000')).toBe('2000-02-01'); - expect(memberTransferService.formatBirthDate('2000-02-01')).toBe('2000-02-01'); - }); -}); diff --git a/backend/tests/myTischtennisFetchLogService.test.js b/backend/tests/myTischtennisFetchLogService.test.js deleted file mode 100644 index 87109b4..0000000 --- a/backend/tests/myTischtennisFetchLogService.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import myTischtennisFetchLogService from '../services/myTischtennisFetchLogService.js'; -import MyTischtennisFetchLog from '../models/MyTischtennisFetchLog.js'; -import { createUser } from './utils/factories.js'; - -describe('myTischtennisFetchLogService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('loggt Abrufe und liefert die letzten Einträge', async () => { - const user = await createUser({ email: 'fetch@example.com' }); - await myTischtennisFetchLogService.logFetch(user.id, 'ratings', true, 'OK', { recordsProcessed: 5 }); - await myTischtennisFetchLogService.logFetch(user.id, 'match_results', false, 'Error', { errorDetails: 'Timeout' }); - - const logs = await myTischtennisFetchLogService.getFetchLogs(user.id, { limit: 10 }); - expect(logs).toHaveLength(2); - expect(logs[0].fetchType).toBe('match_results'); - - const latest = await myTischtennisFetchLogService.getLatestSuccessfulFetches(user.id); - expect(latest.ratings).toBeTruthy(); - expect(latest.match_results).toBeNull(); - }); - - it('aggregiert Statistiken nach Typ', async () => { - const user = await createUser({ email: 'stats@example.com' }); - await myTischtennisFetchLogService.logFetch(user.id, 'ratings', true, 'OK', { recordsProcessed: 3, executionTime: 100 }); - await myTischtennisFetchLogService.logFetch(user.id, 'ratings', false, 'Fail', { recordsProcessed: 0, executionTime: 200 }); - - const stats = await myTischtennisFetchLogService.getFetchStatistics(user.id, 7); - expect(stats).toHaveLength(1); - expect(stats[0].fetchType).toBe('ratings'); - }); -}); diff --git a/backend/tests/myTischtennisRoutes.test.js b/backend/tests/myTischtennisRoutes.test.js deleted file mode 100644 index 5a80e7b..0000000 --- a/backend/tests/myTischtennisRoutes.test.js +++ /dev/null @@ -1,92 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import request from 'supertest'; - -vi.mock('../services/emailService.js', () => ({ - sendActivationEmail: vi.fn().mockResolvedValue(), -})); - -vi.mock('../clients/myTischtennisClient.js', () => ({ - __esModule: true, - default: { - login: vi.fn().mockResolvedValue({ success: true, accessToken: 'token', refreshToken: 'refresh', expiresAt: Date.now() / 1000 + 3600, cookie: 'cookie', user: { id: 1 } }), - getUserProfile: vi.fn().mockResolvedValue({ success: true, clubId: 1, clubName: 'Club', fedNickname: 'fed' }), - }, -})); - -import app from './testApp.js'; -import sequelize from '../database.js'; -import '../models/index.js'; - -import User from '../models/User.js'; -import MyTischtennis from '../models/MyTischtennis.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('MyTischtennis Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt Accounts und liefert Status', async () => { - const { user, credentials } = await registerAndActivate('mytt@example.com'); - const token = await loginAndGetToken(credentials); - - const upsertResponse = await request(app) - .post('/api/mytischtennis/account') - .set(authHeaders(token)) - .send({ email: 'mytt@example.com', password: 'secret', savePassword: true, userPassword: 'Test123!' }); - - expect(upsertResponse.status).toBe(200); - - const statusResponse = await request(app) - .get('/api/mytischtennis/status') - .set(authHeaders(token)); - - expect(statusResponse.status).toBe(200); - expect(statusResponse.body.exists).toBe(true); - - const account = await MyTischtennis.findOne({ where: { userId: user.id } }); - expect(account).toBeTruthy(); - }); - - it('prüft Logins und liefert Sessiondaten', async () => { - const { credentials } = await registerAndActivate('verify@example.com'); - const token = await loginAndGetToken(credentials); - - await request(app) - .post('/api/mytischtennis/account') - .set(authHeaders(token)) - .send({ email: 'verify@example.com', password: 'secret', savePassword: true, userPassword: 'Test123!' }); - - const verifyResponse = await request(app) - .post('/api/mytischtennis/verify') - .set(authHeaders(token)) - .send({ password: 'secret' }); - - expect(verifyResponse.status).toBe(200); - expect(verifyResponse.body.success).toBe(true); - - const sessionResponse = await request(app) - .get('/api/mytischtennis/session') - .set(authHeaders(token)); - - expect(sessionResponse.status).toBe(200); - expect(sessionResponse.body.session.accessToken).toBe('token'); - }); -}); diff --git a/backend/tests/myTischtennisService.test.js b/backend/tests/myTischtennisService.test.js deleted file mode 100644 index 6c05343..0000000 --- a/backend/tests/myTischtennisService.test.js +++ /dev/null @@ -1,70 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../clients/myTischtennisClient.js', () => ({ - __esModule: true, - default: { - login: vi.fn(), - getUserProfile: vi.fn(), - }, -})); - -import sequelize from '../database.js'; -import '../models/index.js'; - -import myTischtennisService from '../services/myTischtennisService.js'; -import MyTischtennis from '../models/MyTischtennis.js'; -import MyTischtennisUpdateHistory from '../models/MyTischtennisUpdateHistory.js'; -import myTischtennisClient from '../clients/myTischtennisClient.js'; -import { createUser } from './utils/factories.js'; - -const clientMock = /** @type {{ login: vi.Mock; getUserProfile: vi.Mock }} */ (myTischtennisClient); - -describe('myTischtennisService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - clientMock.login.mockResolvedValue({ success: true, accessToken: 'token', refreshToken: 'refresh', expiresAt: Date.now() / 1000 + 3600, cookie: 'cookie', user: { id: 1 } }); - clientMock.getUserProfile.mockResolvedValue({ success: true, clubId: 123, clubName: 'Club', fedNickname: 'fed' }); - }); - - it('legt Accounts an und aktualisiert Logins', async () => { - const user = await createUser({ email: 'user@example.com' }); - const userId = user.id; - - const account = await myTischtennisService.upsertAccount(userId, 'user@example.com', 'pass', true, true, 'Secret!123'); - expect(account.email).toBe('user@example.com'); - - const stored = await MyTischtennis.findOne({ where: { userId } }); - expect(stored.savePassword).toBe(true); - expect(clientMock.getUserProfile).toHaveBeenCalled(); - expect(clientMock.login).toHaveBeenCalledWith('user@example.com', 'pass'); - }); - - it('verifiziert Logins mit gespeicherter Session', async () => { - const user = await createUser({ email: 'session@example.com' }); - const userId = user.id; - - clientMock.login.mockResolvedValueOnce({ success: true, accessToken: 'token', refreshToken: 'refresh', expiresAt: Date.now() / 1000 + 3600, cookie: 'cookie', user: { id: 2 } }); - - await myTischtennisService.upsertAccount(userId, 'session@example.com', 'pass', true, false, 'Secret!123'); - - const result = await myTischtennisService.verifyLogin(userId); - expect(result.success).toBe(true); - expect(clientMock.login).toHaveBeenCalledWith('session@example.com', 'pass'); - }); - - it('speichert Update-History und setzt lastUpdateRatings', async () => { - const user = await createUser({ email: 'history@example.com' }); - const userId = user.id; - await MyTischtennis.create({ userId, email: 'history@example.com' }); - - await myTischtennisService.logUpdateAttempt(userId, true, 'OK', null, 5, 1234); - - const history = await MyTischtennisUpdateHistory.findAll({ where: { userId } }); - expect(history).toHaveLength(1); - expect(history[0].updatedCount).toBe(5); - - const account = await MyTischtennis.findOne({ where: { userId } }); - expect(account.lastUpdateRatings).toBeTruthy(); - }); -}); diff --git a/backend/tests/myTischtennisUrlRoutes.test.js b/backend/tests/myTischtennisUrlRoutes.test.js deleted file mode 100644 index 930a351..0000000 --- a/backend/tests/myTischtennisUrlRoutes.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import request from 'supertest'; - -vi.mock('../services/emailService.js', () => ({ - sendActivationEmail: vi.fn().mockResolvedValue(), -})); - -vi.mock('../clients/myTischtennisClient.js', () => ({ - __esModule: true, - default: { - login: vi.fn().mockResolvedValue({ success: true, accessToken: 'token', refreshToken: 'refresh', expiresAt: Date.now() / 1000 + 3600, cookie: 'cookie', user: { id: 1 } }), - getUserProfile: vi.fn().mockResolvedValue({ success: true, clubId: 1, clubName: 'Club', fedNickname: 'fed' }), - }, -})); - -import app from './testApp.js'; -import sequelize from '../database.js'; -import '../models/index.js'; - -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import League from '../models/League.js'; -import Season from '../models/Season.js'; -import ClubTeam from '../models/ClubTeam.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; -}; - -const authHeaders = (token, userId) => ({ - Authorization: `Bearer ${token}`, - authcode: token, - userid: String(userId), -}); - -describe('MyTischtennis URL Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('parst URLs und konfiguriert Teams', async () => { - const { user, credentials } = await registerAndActivate('url@example.com'); - const token = await loginAndGetToken(credentials); - - const club = await Club.create({ name: 'URL Club' }); - const season = await Season.create({ season: '2024/2025' }); - const league = await League.create({ name: 'Verbandsliga', association: 'HeTTV', groupname: 'Verbandsliga', myTischtennisGroupId: '12345', seasonId: season.id, clubId: club.id }); - const team = await ClubTeam.create({ name: 'URL Team', clubId: club.id, leagueId: league.id, seasonId: season.id }); - - const configureResponse = await request(app) - .post('/api/mytischtennis/configure-team') - .set(authHeaders(token, user.id)) - .send({ - url: 'https://www.mytischtennis.de/click-tt/HeTTV/24--25/ligen/Verbandsliga/gruppe/12345/mannschaft/67890/URL_Team/spielerbilanzen/gesamt', - clubTeamId: team.id, - createLeague: true, - createSeason: true, - }); - - expect(configureResponse.status).toBe(200); - await team.reload(); - expect(team.myTischtennisTeamId).toBeTruthy(); - }); - - it('gibt Team-URLs zurück', async () => { - const { user, credentials } = await registerAndActivate('teamurl@example.com'); - const token = await loginAndGetToken(credentials); - - const club = await Club.create({ name: 'URL Club' }); - const season = await Season.create({ season: '2024/2025' }); - const league = await League.create({ name: 'Verbandsliga', association: 'HeTTV', groupname: 'Verbandsliga', myTischtennisGroupId: '12345', seasonId: season.id, clubId: club.id }); - const team = await ClubTeam.create({ name: 'URL Team', clubId: club.id, leagueId: league.id, seasonId: season.id, myTischtennisTeamId: '98765' }); - - const urlResponse = await request(app) - .get(`/api/mytischtennis/team-url/${team.id}`) - .set(authHeaders(token, user.id)); - - expect(urlResponse.status).toBe(200); - expect(urlResponse.body.url).toContain('98765'); - }); -}); diff --git a/backend/tests/pdfParserService.test.js b/backend/tests/pdfParserService.test.js deleted file mode 100644 index 1ee5d78..0000000 --- a/backend/tests/pdfParserService.test.js +++ /dev/null @@ -1,124 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import fs from 'fs'; -import os from 'os'; -import path from 'path'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import pdfParserService from '../services/pdfParserService.js'; -import Club from '../models/Club.js'; -import Team from '../models/Team.js'; -import League from '../models/League.js'; -import Season from '../models/Season.js'; -import Match from '../models/Match.js'; -import Location from '../models/Location.js'; - -describe('pdfParserService', () => { - let tempFiles = []; - - beforeEach(async () => { - await sequelize.sync({ force: true }); - tempFiles = []; - }); - - afterEach(() => { - tempFiles.forEach((file) => { - try { - fs.unlinkSync(file); - } catch { - /* ignore */ - } - }); - }); - - const createTempFile = (content, ext = '.txt') => { - const filePath = path.join(os.tmpdir(), `pdfparser-${Date.now()}-${Math.random().toString(16).slice(2)}${ext}`); - fs.writeFileSync(filePath, content, 'utf8'); - tempFiles.push(filePath); - return filePath; - }; - - it('parst Standard-Format aus einer Textdatei', async () => { - const lines = [ - 'Sa. 06.09.2025 10:00 Harheimer TC II SG Teststadt III ABC123DEF456' - ]; - const filePath = createTempFile(lines.join('\n')); - - const result = await pdfParserService.parsePDF(filePath, 1); - - expect(result.matches).toHaveLength(1); - const match = result.matches[0]; - expect(match.homeTeamName).toBe('Harheimer TC II'); - expect(match.guestTeamName).toBe('SG Teststadt III'); - expect(match.code).toBe('ABC123DEF456'); - }); - - it('parst Tabellen-Format', () => { - const text = [ - 'Datum Zeit Heim Gast Code Heim-Pin Gast-Pin', - '06.09.2025 10:00 Harheimer TC II SG Teststadt III ABC123DEF456 1234 5678' - ].join('\n'); - - const result = pdfParserService.extractMatchData(text, 1); - - expect(result.matches).toHaveLength(1); - const match = result.matches[0]; - expect(match.homeTeamName).toContain('Harheimer'); - expect(['1234', '5678', null, ''].includes(match.guestPin ?? null)).toBe(true); - expect(['1234', '5678', null, ''].includes(match.homePin ?? null)).toBe(true); - }); - - it('parst Listen-Format', () => { - const text = '1. 06.09.2025 10:00 Harheimer TC II vs SG Teststadt III code ABC123DEF456'; - - const result = pdfParserService.extractMatchData(text, 5); - - expect(result.matches).toHaveLength(1); - expect(result.matches[0].guestTeamName).toContain('SG Teststadt III'); - }); - - it('speichert Matches in der Datenbank und aktualisiert vorhandene Einträge', async () => { - const club = await Club.create({ name: 'Harheimer TC' }); - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'Verbandsliga', clubId: club.id, seasonId: season.id }); - const homeTeam = await Team.create({ name: 'Harheimer TC II', clubId: club.id, leagueId: league.id, seasonId: season.id }); - const guestTeam = await Team.create({ name: 'SG Teststadt III', clubId: club.id, leagueId: league.id, seasonId: season.id }); - await Location.create({ name: 'Halle', address: 'Straße 1', city: 'Stadt', zip: '12345' }); - - const matchDate = new Date('2025-09-06T10:00:00Z'); - const matches = [ - { - date: matchDate, - time: '10:00', - homeTeamName: homeTeam.name, - guestTeamName: guestTeam.name, - code: 'ABC123DEF456', - clubId: club.id, - rawLine: 'Sa. 06.09.2025 10:00 Harheimer TC II SG Teststadt III ABC123DEF456' - } - ]; - - const createResult = await pdfParserService.saveMatchesToDatabase(matches, league.id); - expect(createResult.created).toBe(1); - expect(createResult.updated).toBe(0); - expect(await Match.count()).toBe(1); - - const updateMatches = [ - { - date: matchDate, - time: '10:00', - homeTeamName: homeTeam.name, - guestTeamName: guestTeam.name, - code: 'XYZ987654321', - clubId: club.id, - rawLine: 'Sa. 06.09.2025 10:00 Harheimer TC II SG Teststadt III XYZ987654321' - } - ]; - - const updateResult = await pdfParserService.saveMatchesToDatabase(updateMatches, league.id); - expect(updateResult.updated).toBe(1); - const storedMatch = await Match.findOne(); - expect(storedMatch.code).toBe('XYZ987654321'); - }); -}); diff --git a/backend/tests/permissionRoutes.test.js b/backend/tests/permissionRoutes.test.js deleted file mode 100644 index 4210aca..0000000 --- a/backend/tests/permissionRoutes.test.js +++ /dev/null @@ -1,159 +0,0 @@ -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'; - -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('Permission Routes', () => { - beforeEach(async () => { - vi.clearAllMocks(); - await sequelize.sync({ force: true }); - }); - - it('liefert die verfügbaren Rollen für authentifizierte Nutzer', async () => { - const { credentials } = await registerAndActivate('roles@example.com'); - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .get('/api/permissions/roles/available') - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(200); - expect(Array.isArray(response.body)).toBe(true); - expect(response.body.length).toBeGreaterThan(0); - }); - - it('gibt Club-Mitglieder und Berechtigungen für Administratoren zurück', async () => { - const { user: owner, credentials } = await registerAndActivate('owner@example.com'); - const { user: member } = await registerAndActivate('member@example.com'); - const club = await Club.create({ name: 'Permissions Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .get(`/api/permissions/${club.id}/members`) - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(200); - expect(response.body).toHaveLength(2); - const memberEntry = response.body.find((entry) => entry.userId === member.id); - expect(memberEntry).toBeTruthy(); - expect(memberEntry.role).toBe('member'); - }); - - it('verweigert den Zugriff auf die Mitgliederliste für einfache Mitglieder', async () => { - const { user: owner } = await registerAndActivate('owner2@example.com'); - const { user: member, credentials } = await registerAndActivate('member2@example.com'); - const club = await Club.create({ name: 'Restricted Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .get(`/api/permissions/${club.id}/members`) - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(403); - }); - - it('ändert den Genehmigungsstatus eines Mitglieds über die API', async () => { - const { user: owner, credentials } = await registerAndActivate('owner3@example.com'); - const { user: member } = await registerAndActivate('member3@example.com'); - const club = await Club.create({ name: 'Status Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .put(`/api/permissions/${club.id}/user/${member.id}/status`) - .set('Authorization', `Bearer ${token}`) - .send({ approved: false }); - - expect(response.status).toBe(200); - const updated = await UserClub.findOne({ where: { userId: member.id, clubId: club.id } }); - expect(updated.approved).toBe(false); - }); - - it('ändert Rollen und benutzerdefinierte Berechtigungen', async () => { - const { user: owner, credentials } = await registerAndActivate('owner4@example.com'); - const { user: member } = await registerAndActivate('member4@example.com'); - const club = await Club.create({ name: 'Role Club' }); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const token = await loginAndGetToken(credentials); - - const roleResponse = await request(app) - .put(`/api/permissions/${club.id}/user/${member.id}/role`) - .set('Authorization', `Bearer ${token}`) - .send({ role: 'trainer' }); - - expect(roleResponse.status).toBe(200); - const roleUpdated = await UserClub.findOne({ where: { userId: member.id, clubId: club.id } }); - expect(roleUpdated.role).toBe('trainer'); - - const customPermissions = { diary: { read: true, write: true }, schedule: { read: true } }; - const permissionResponse = await request(app) - .put(`/api/permissions/${club.id}/user/${member.id}/permissions`) - .set('Authorization', `Bearer ${token}`) - .send({ permissions: customPermissions }); - - expect(permissionResponse.status).toBe(200); - await roleUpdated.reload(); - expect(roleUpdated.permissions.diary.write).toBe(true); - - const ownPermissions = await request(app) - .get(`/api/permissions/${club.id}`) - .set('Authorization', `Bearer ${token}`); - - expect(ownPermissions.status).toBe(200); - expect(ownPermissions.body.permissions.diary.write).toBe(true); - }); - - it('liefert 400 bei ungültiger Club-ID', async () => { - const { credentials } = await registerAndActivate('invalidclub@example.com'); - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .get('/api/permissions/not-a-number') - .set('Authorization', `Bearer ${token}`); - - expect(response.status).toBe(400); - }); -}); diff --git a/backend/tests/permissionService.test.js b/backend/tests/permissionService.test.js deleted file mode 100644 index 1112a23..0000000 --- a/backend/tests/permissionService.test.js +++ /dev/null @@ -1,128 +0,0 @@ -import { describe, it, expect, beforeAll, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import UserClub from '../models/UserClub.js'; -import permissionService from '../services/permissionService.js'; - -async function createUser(email, password = 'Secret!123', overrides = {}) { - return User.create({ email, password, isActive: true, ...overrides }); -} - -async function createClub(name = 'Testclub') { - return Club.create({ name }); -} - -describe('permissionService', () => { - beforeAll(async () => { - await sequelize.sync({ force: true }); - }); - - beforeEach(async () => { - await sequelize.truncate({ cascade: true, restartIdentity: true }); - }); - - it('liefert Admin-Rechte für Club-Eigentümer', async () => { - const owner = await createUser('owner@test.de'); - const club = await createClub('Admin Club'); - - await UserClub.create({ - userId: owner.id, - clubId: club.id, - role: 'admin', - approved: true, - isOwner: true, - }); - - const permissions = await permissionService.getUserClubPermissions(owner.id, club.id); - expect(permissions).toMatchObject({ isOwner: true, role: 'admin' }); - expect(permissions.permissions.permissions.write).toBe(true); - }); - - it('verhindert Rollenänderung ohne Berechtigung', async () => { - const owner = await createUser('owner@test.de'); - const member = await createUser('member@test.de'); - const club = await createClub('Club'); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - await expect( - permissionService.setUserRole(owner.id, club.id, 'trainer', member.id) - ).rejects.toThrow('Keine Berechtigung zum Ändern von Rollen'); - }); - - it('erlaubt Administratoren die Rollenänderung', async () => { - const owner = await createUser('owner@test.de'); - const admin = await createUser('admin@test.de'); - const member = await createUser('member@test.de'); - const club = await createClub('Club'); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: admin.id, clubId: club.id, role: 'admin', approved: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - await permissionService.setUserRole(member.id, club.id, 'trainer', admin.id); - - const updated = await UserClub.findOne({ where: { userId: member.id, clubId: club.id } }); - expect(updated.role).toBe('trainer'); - }); - - it('erlaubt Custom-Permissions und fasst sie zusammen', async () => { - const owner = await createUser('owner@test.de'); - const member = await createUser('member@test.de'); - const club = await createClub('Club'); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - await permissionService.setCustomPermissions( - member.id, - club.id, - { members: { read: true, write: true } }, - owner.id - ); - - const permissions = await permissionService.getUserClubPermissions(member.id, club.id); - expect(permissions.permissions.members.write).toBe(true); - }); - - it('liefert Fehlermeldung wenn Berechtigungsübersicht ohne Rechte angefordert wird', async () => { - const owner = await createUser('owner@test.de'); - const member = await createUser('member@test.de'); - const club = await createClub('Club'); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - await expect( - permissionService.getClubMembersWithPermissions(club.id, member.id) - ).rejects.toThrow('Keine Berechtigung zum Anzeigen von Berechtigungen'); - }); - - it('liefert Mitgliederliste mit effektiven Berechtigungen für Administratoren', async () => { - const owner = await createUser('owner@test.de'); - const member = await createUser('member@test.de'); - const club = await createClub('Club'); - - await UserClub.bulkCreate([ - { userId: owner.id, clubId: club.id, role: 'admin', approved: true, isOwner: true }, - { userId: member.id, clubId: club.id, role: 'member', approved: true }, - ]); - - const members = await permissionService.getClubMembersWithPermissions(club.id, owner.id); - expect(members).toHaveLength(2); - const memberEntry = members.find((entry) => entry.userId === member.id); - expect(memberEntry).toBeTruthy(); - expect(memberEntry.effectivePermissions.statistics.read).toBe(true); - }); -}); diff --git a/backend/tests/predefinedActivityRoutes.test.js b/backend/tests/predefinedActivityRoutes.test.js deleted file mode 100644 index 3e2067d..0000000 --- a/backend/tests/predefinedActivityRoutes.test.js +++ /dev/null @@ -1,94 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.js'; -import PredefinedActivity from '../models/PredefinedActivity.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('PredefinedActivity Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt, aktualisiert und listet Aktivitäten', async () => { - const { credentials } = await registerAndActivate('predefined@example.com'); - const token = await loginAndGetToken(credentials); - - const createResponse = await request(app) - .post('/api/predefined-activities') - .set(authHeaders(token)) - .send({ name: 'Aufwärmen', code: 'AW', description: 'Kurz' }); - expect(createResponse.status).toBe(201); - const activityId = createResponse.body.id; - - const updateResponse = await request(app) - .put(`/api/predefined-activities/${activityId}`) - .set(authHeaders(token)) - .send({ name: 'Aufwärmen Lang', code: 'AWL' }); - - expect(updateResponse.status).toBe(200); - expect(updateResponse.body.code).toBe('AWL'); - - const listResponse = await request(app) - .get('/api/predefined-activities') - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body.some((entry) => entry.name === 'Aufwärmen Lang')).toBe(true); - }); - - it('sucht, merged und dedupliziert Aktivitäten', async () => { - const { credentials } = await registerAndActivate('predefined2@example.com'); - const token = await loginAndGetToken(credentials); - - const a1 = await PredefinedActivity.create({ name: 'Fangspiel', code: 'FANG' }); - const a2 = await PredefinedActivity.create({ name: 'Fangspiel', code: 'FANG2' }); - - const searchResponse = await request(app) - .get('/api/predefined-activities/search/query') - .set(authHeaders(token)) - .query({ q: 'FANG' }); - - expect(searchResponse.status).toBe(200); - expect(searchResponse.body.length).toBeGreaterThan(0); - - const mergeResponse = await request(app) - .post('/api/predefined-activities/merge') - .set(authHeaders(token)) - .send({ sourceId: a2.id, targetId: a1.id }); - - expect(mergeResponse.status).toBe(200); - expect(await PredefinedActivity.findByPk(a2.id)).toBeNull(); - - const dedupResponse = await request(app) - .post('/api/predefined-activities/deduplicate') - .set(authHeaders(token)); - - expect(dedupResponse.status).toBe(200); - }); -}); diff --git a/backend/tests/predefinedActivityService.test.js b/backend/tests/predefinedActivityService.test.js deleted file mode 100644 index 04b6a83..0000000 --- a/backend/tests/predefinedActivityService.test.js +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import predefinedActivityService from '../services/predefinedActivityService.js'; -import PredefinedActivity from '../models/PredefinedActivity.js'; -import PredefinedActivityImage from '../models/PredefinedActivityImage.js'; -import DiaryDateActivity from '../models/DiaryDateActivity.js'; -import DiaryDate from '../models/DiaryDates.js'; -import Club from '../models/Club.js'; - -describe('predefinedActivityService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt und aktualisiert Aktivitäten mit Zeichnungsdaten', async () => { - const created = await predefinedActivityService.createPredefinedActivity({ - name: 'Aufwärmen', - code: 'AW', - description: 'Kurzes Warmup', - duration: 15, - drawingData: { lines: 3 }, - }); - - expect(created.name).toBe('Aufwärmen'); - expect(created.drawingData).toBe(JSON.stringify({ lines: 3 })); - - const updated = await predefinedActivityService.updatePredefinedActivity(created.id, { - name: 'Aufwärmen Intensiv', - code: 'AWI', - description: 'Intensives Warmup', - duration: 20, - drawingData: { lines: 5 }, - }); - - expect(updated.name).toBe('Aufwärmen Intensiv'); - expect(updated.code).toBe('AWI'); - }); - - it('sucht Aktivitäten anhand der Kürzel', async () => { - await PredefinedActivity.bulkCreate([ - { name: 'Fangspiel', code: 'FANG' }, - { name: 'Ballkontrolle', code: 'BALL' }, - { name: 'Freies Spiel', code: null }, - ]); - - const result = await predefinedActivityService.searchPredefinedActivities('FA'); - expect(result).toHaveLength(1); - expect(result[0].code).toBe('FANG'); - }); - - it('führt Aktivitäten zusammen und passt Referenzen an', async () => { - const club = await Club.create({ name: 'Aktivitätsclub' }); - const source = await PredefinedActivity.create({ name: 'Topspin', code: 'TS' }); - const target = await PredefinedActivity.create({ name: 'Topspin Verbesserte', code: 'TSV' }); - - const diaryDate = await DiaryDate.create({ date: new Date('2025-09-01'), clubId: club.id }); - const diaryDateActivity = await DiaryDateActivity.create({ - diaryDateId: diaryDate.id, - predefinedActivityId: source.id, - isTimeblock: false, - orderId: 1, - }); - await PredefinedActivityImage.create({ predefinedActivityId: source.id, imagePath: 'uploads/test.png' }); - - await predefinedActivityService.mergeActivities(source.id, target.id); - - await diaryDateActivity.reload(); - expect(diaryDateActivity.predefinedActivityId).toBe(target.id); - const images = await PredefinedActivityImage.findAll({ where: { predefinedActivityId: target.id } }); - expect(images).toHaveLength(1); - expect(await PredefinedActivity.findByPk(source.id)).toBeNull(); - }); - - it('dedupliziert Aktivitäten nach Namen', async () => { - await PredefinedActivity.bulkCreate([ - { name: 'Schmetterball', code: 'SM1' }, - { name: 'schmetterball', code: 'SM2' }, - { name: 'Aufschlag', code: 'AS' }, - ]); - - const result = await predefinedActivityService.deduplicateActivities(); - expect(result.mergedCount).toBe(1); - const remaining = await PredefinedActivity.findAll(); - expect(remaining).toHaveLength(2); - }); -}); diff --git a/backend/tests/schedulerService.test.js b/backend/tests/schedulerService.test.js deleted file mode 100644 index aa30f5f..0000000 --- a/backend/tests/schedulerService.test.js +++ /dev/null @@ -1,125 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; - -const { mockJobs } = vi.hoisted(() => ({ mockJobs: [] })); - -vi.mock('node-cron', () => { - const schedule = vi.fn((expression, handler) => { - const job = { - expression, - handler, - start: vi.fn(), - stop: vi.fn(), - }; - mockJobs.push(job); - return job; - }); - - return { - __esModule: true, - default: { - schedule, - __mockJobs: mockJobs, - }, - }; -}); - -vi.mock('../services/autoUpdateRatingsService.js', () => ({ - __esModule: true, - default: { - executeAutomaticUpdates: vi.fn().mockResolvedValue({ updatedCount: 3 }), - }, -})); - -vi.mock('../services/autoFetchMatchResultsService.js', () => ({ - __esModule: true, - default: { - executeAutomaticFetch: vi.fn().mockResolvedValue({ fetchedCount: 4 }), - }, -})); - -vi.mock('../services/apiLogService.js', () => ({ - __esModule: true, - default: { - logSchedulerExecution: vi.fn().mockResolvedValue(), - }, -})); - -import schedulerService from '../services/schedulerService.js'; -import cron from 'node-cron'; -import autoUpdateRatingsService from '../services/autoUpdateRatingsService.js'; -import autoFetchMatchResultsService from '../services/autoFetchMatchResultsService.js'; -import apiLogService from '../services/apiLogService.js'; - -const cronMock = /** @type {vi.Mock} */ (cron.schedule); -const autoUpdateMock = /** @type {vi.Mock} */ (autoUpdateRatingsService.executeAutomaticUpdates); -const autoFetchMock = /** @type {vi.Mock} */ (autoFetchMatchResultsService.executeAutomaticFetch); -const apiLogMock = /** @type {vi.Mock} */ (apiLogService.logSchedulerExecution); - -describe('schedulerService', () => { - beforeEach(() => { - schedulerService.stop(); - mockJobs.length = 0; - cronMock.mockClear(); - autoUpdateMock.mockClear(); - autoFetchMock.mockClear(); - apiLogMock.mockClear(); - }); - - afterEach(() => { - schedulerService.stop(); - }); - - it('startet Scheduler und registriert Cron-Jobs genau einmal', () => { - schedulerService.start(); - expect(cronMock.mock.calls).toHaveLength(2); - expect(schedulerService.getStatus()).toMatchObject({ isRunning: true, jobs: ['ratingUpdates', 'matchResults'] }); - - schedulerService.start(); - expect(cronMock.mock.calls).toHaveLength(2); - }); - - it('stoppt Scheduler und ruft stop für jede Aufgabe auf', () => { - schedulerService.start(); - const jobsSnapshot = [...mockJobs]; - schedulerService.stop(); - - jobsSnapshot.forEach((job) => { - expect(job.stop).toHaveBeenCalled(); - }); - expect(schedulerService.getStatus().isRunning).toBe(false); - }); - - it('triggert manuelle Updates und Fetches', async () => { - const ratings = await schedulerService.triggerRatingUpdates(); - expect(ratings.success).toBe(true); - expect(autoUpdateMock).toHaveBeenCalled(); - - const matches = await schedulerService.triggerMatchResultsFetch(); - expect(matches.success).toBe(true); - expect(autoFetchMock).toHaveBeenCalled(); - }); - - it('führt geplante Jobs aus und protokolliert Ergebnisse', async () => { - schedulerService.start(); - - const [ratingJob, matchJob] = mockJobs; - - await ratingJob.handler(); - expect(apiLogMock).toHaveBeenCalledWith( - 'rating_updates', - true, - expect.any(Object), - expect.any(Number), - null - ); - - await matchJob.handler(); - expect(apiLogMock).toHaveBeenCalledWith( - 'match_results', - true, - expect.any(Object), - expect.any(Number), - null - ); - }); -}); diff --git a/backend/tests/seasonRoutes.test.js b/backend/tests/seasonRoutes.test.js deleted file mode 100644 index 6861897..0000000 --- a/backend/tests/seasonRoutes.test.js +++ /dev/null @@ -1,94 +0,0 @@ -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 '../models/index.js'; - -import Season from '../models/Season.js'; -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Season Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('legt Saisons an, listet und löscht sie', async () => { - const { credentials } = await registerAndActivate('season@example.com'); - const token = await loginAndGetToken(credentials); - - const createResponse = await request(app) - .post('/api/seasons') - .set(authHeaders(token)) - .send({ season: '2022/2023' }); - - expect(createResponse.status).toBe(201); - const seasonId = createResponse.body.id; - - const listResponse = await request(app) - .get('/api/seasons') - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body.some((entry) => entry.season === '2022/2023')).toBe(true); - - const getResponse = await request(app) - .get(`/api/seasons/${seasonId}`) - .set(authHeaders(token)); - - expect(getResponse.status).toBe(200); - - const deleteResponse = await request(app) - .delete(`/api/seasons/${seasonId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - expect(deleteResponse.body.message).toBe('deleted'); - }); - - it('validiert Saison-Format', async () => { - const { credentials } = await registerAndActivate('season-invalid@example.com'); - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .post('/api/seasons') - .set(authHeaders(token)) - .send({ season: 'invalid' }); - - expect(response.status).toBe(400); - }); - - it('gibt die aktuelle Saison zurück (mit Auto-Erstellung)', async () => { - const { credentials } = await registerAndActivate('season-current@example.com'); - const token = await loginAndGetToken(credentials); - - const response = await request(app) - .get('/api/seasons/current') - .set(authHeaders(token)); - - expect(response.status).toBe(200); - expect(response.body).toHaveProperty('season'); - }); -}); diff --git a/backend/tests/seasonService.test.js b/backend/tests/seasonService.test.js deleted file mode 100644 index 1d8ba7b..0000000 --- a/backend/tests/seasonService.test.js +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import SeasonService from '../services/seasonService.js'; -import Season from '../models/Season.js'; -import Team from '../models/Team.js'; -import League from '../models/League.js'; -import Club from '../models/Club.js'; - -describe('seasonService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt die aktuelle Saison nur einmal', async () => { - const first = await SeasonService.getOrCreateCurrentSeason(); - const second = await SeasonService.getOrCreateCurrentSeason(); - - expect(first.id).toBe(second.id); - }); - - it('verhindert doppelte Saisons und löscht ungenutzte Einträge', async () => { - const seasonName = '2024/2025'; - const created = await SeasonService.createSeason(seasonName); - expect(created.season).toBe(seasonName); - - await expect(SeasonService.createSeason(seasonName)).rejects.toThrow('Season already exists'); - - const deleteResult = await SeasonService.deleteSeason(created.id); - expect(deleteResult).toBe(true); - }); - - it('verhindert das Löschen, wenn Teams oder Ligen referenzieren', async () => { - const club = await Club.create({ name: 'Season Club' }); - const season = await SeasonService.createSeason('2023/2024'); - await League.create({ name: 'Testliga', clubId: club.id, seasonId: season.id }); - await Team.create({ name: 'Testteam', clubId: club.id, seasonId: season.id }); - - await expect(SeasonService.deleteSeason(season.id)).rejects.toThrow('Season is used by teams'); - - await Team.destroy({ where: { seasonId: season.id } }); - await expect(SeasonService.deleteSeason(season.id)).rejects.toThrow('Season is used by leagues'); - - await League.destroy({ where: { seasonId: season.id } }); - const deleted = await SeasonService.deleteSeason(season.id); - expect(deleted).toBe(true); - }); -}); diff --git a/backend/tests/sessionRoutes.test.js b/backend/tests/sessionRoutes.test.js deleted file mode 100644 index 4c42ad5..0000000 --- a/backend/tests/sessionRoutes.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; -import request from 'supertest'; - -vi.mock('../services/emailService.js', () => ({ - sendActivationEmail: vi.fn().mockResolvedValue(), -})); - -const mockScheduler = vi.hoisted(() => ({ - triggerRatingUpdates: vi.fn().mockResolvedValue({ success: true }), - triggerMatchResultsFetch: vi.fn().mockResolvedValue({ success: true }), - getStatus: vi.fn().mockReturnValue({ isRunning: true, jobs: ['ratingUpdates'] }), - getNextRatingUpdateTime: vi.fn().mockReturnValue(new Date('2025-09-01T06:00:00Z')), -})); - -vi.mock('../services/schedulerService.js', () => ({ - __esModule: true, - default: mockScheduler, -})); - -const mockSessionService = vi.hoisted(() => ({ - isSessionValid: vi.fn().mockResolvedValue(true), -})); - -vi.mock('../services/sessionService.js', () => ({ - __esModule: true, - default: mockSessionService, -})); - -import app from './testApp.js'; -import sequelize from '../database.js'; -import '../models/index.js'; - -import User from '../models/User.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Session Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - vi.clearAllMocks(); - mockSessionService.isSessionValid.mockResolvedValue(true); - }); - - it('prüft die Session und liefert Statusinformationen', async () => { - const { credentials } = await registerAndActivate('session@example.com'); - const token = await loginAndGetToken(credentials); - - const statusResponse = await request(app) - .get('/api/session/status') - .set(authHeaders(token)); - - expect(mockSessionService.isSessionValid).toHaveBeenCalled(); - expect(statusResponse.status).toBe(200); - expect(statusResponse.body.valid).toBe(true); - - const schedulerResponse = await request(app) - .get('/api/session/scheduler-status') - .set(authHeaders(token)); - - expect(schedulerResponse.status).toBe(200); - expect(schedulerResponse.body.isRunning).toBe(true); - }); - - it('triggert Scheduler-Aktionen manuell', async () => { - const { credentials } = await registerAndActivate('session-trigger@example.com'); - const token = await loginAndGetToken(credentials); - - const ratingResponse = await request(app) - .post('/api/session/trigger-rating-updates') - .set(authHeaders(token)); - - expect(ratingResponse.status).toBe(200); - expect(mockScheduler.triggerRatingUpdates).toHaveBeenCalled(); - - const matchResponse = await request(app) - .post('/api/session/trigger-match-fetch') - .set(authHeaders(token)); - - expect(matchResponse.status).toBe(200); - expect(mockScheduler.triggerMatchResultsFetch).toHaveBeenCalled(); - }); -}); diff --git a/backend/tests/setupTestEnv.js b/backend/tests/setupTestEnv.js deleted file mode 100644 index a6175d2..0000000 --- a/backend/tests/setupTestEnv.js +++ /dev/null @@ -1,15 +0,0 @@ -import { afterAll } from 'vitest'; -import sequelize from '../database.js'; -import '../models/index.js'; - -process.env.NODE_ENV = process.env.NODE_ENV || 'test'; -process.env.DB_DIALECT = process.env.DB_DIALECT || 'sqlite'; -process.env.DB_STORAGE = process.env.DB_STORAGE || ':memory:'; -process.env.JWT_SECRET = process.env.JWT_SECRET || 'test-secret'; -process.env.EMAIL_USER = process.env.EMAIL_USER || 'noreply@example.com'; -process.env.BASE_URL = process.env.BASE_URL || 'http://localhost:3000'; -process.env.ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; - -afterAll(async () => { - await sequelize.close(); -}); diff --git a/backend/tests/teamDocumentRoutes.test.js b/backend/tests/teamDocumentRoutes.test.js deleted file mode 100644 index c5c2b7b..0000000 --- a/backend/tests/teamDocumentRoutes.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; -import request from 'supertest'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; - -vi.mock('../services/emailService.js', () => ({ - sendActivationEmail: vi.fn().mockResolvedValue(), -})); - -import app from './testApp.js'; -import sequelize from '../database.js'; -import '../models/index.js'; - -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import Season from '../models/Season.js'; -import League from '../models/League.js'; -import ClubTeam from '../models/ClubTeam.js'; -import UserClub from '../models/UserClub.js'; -import TeamDocument from '../models/TeamDocument.js'; - -const uploadBaseDir = path.join(process.cwd(), 'uploads'); - -const ensureCleanUploads = () => { - fs.rmSync(uploadBaseDir, { recursive: true, force: true }); -}; - -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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('TeamDocument Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - ensureCleanUploads(); - }); - - afterEach(() => { - ensureCleanUploads(); - }); - - it('lädt Team-Dokumente hoch, listet und löscht sie', async () => { - const { user, credentials } = await registerAndActivate('teamdoc@example.com'); - const token = await loginAndGetToken(credentials); - - const club = await Club.create({ name: 'DokClub' }); - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'DokLiga', clubId: club.id, seasonId: season.id }); - const team = await ClubTeam.create({ name: 'DokTeam', clubId: club.id, leagueId: league.id, seasonId: season.id }); - await UserClub.create({ userId: user.id, clubId: club.id, role: 'admin', approved: true }); - - const tempUploadPath = path.join(os.tmpdir(), `teamdoc-${Date.now()}.txt`); - fs.writeFileSync(tempUploadPath, 'Sa. 06.09.2025 10:00 Harheimer TC II SG Teststadt III ABC123DEF456', 'utf8'); - - const uploadResponse = await request(app) - .post(`/api/team-documents/club-team/${team.id}/upload`) - .set(authHeaders(token)) - .field('documentType', 'code_list') - .attach('document', tempUploadPath, { filename: 'codes.txt', contentType: 'text/plain' }); - - fs.unlinkSync(tempUploadPath); - expect(uploadResponse.status).toBe(201); - const documentId = uploadResponse.body.id; - expect(await TeamDocument.count()).toBe(1); - - const listResponse = await request(app) - .get(`/api/team-documents/club-team/${team.id}`) - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - - const getResponse = await request(app) - .get(`/api/team-documents/${documentId}`) - .set(authHeaders(token)); - - expect(getResponse.status).toBe(200); - - const parseResponse = await request(app) - .post(`/api/team-documents/${documentId}/parse`) - .query({ leagueid: league.id }) - .set(authHeaders(token)); - - expect(parseResponse.status).toBe(200); - expect(parseResponse.body.parseResult.matchesFound).toBeDefined(); - - const deleteResponse = await request(app) - .delete(`/api/team-documents/${documentId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - expect(await TeamDocument.count()).toBe(0); - }); -}); diff --git a/backend/tests/teamDocumentService.test.js b/backend/tests/teamDocumentService.test.js deleted file mode 100644 index fa6bb2c..0000000 --- a/backend/tests/teamDocumentService.test.js +++ /dev/null @@ -1,102 +0,0 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import fs from 'fs'; -import os from 'os'; -import path from 'path'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import TeamDocumentService from '../services/teamDocumentService.js'; -import TeamDocument from '../models/TeamDocument.js'; -import Club from '../models/Club.js'; -import Season from '../models/Season.js'; -import League from '../models/League.js'; -import ClubTeam from '../models/ClubTeam.js'; - -const uploadBaseDir = path.join(process.cwd(), 'uploads'); - -const ensureCleanUploads = () => { - fs.rmSync(uploadBaseDir, { recursive: true, force: true }); -}; - -const createTempFile = (content = 'test', extension = '.txt') => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'teamdoc-')); - const filePath = path.join(tmpDir, `file${extension}`); - fs.writeFileSync(filePath, content); - return { tmpDir, filePath }; -}; - -const createClubTeam = async () => { - const club = await Club.create({ name: 'Upload Club' }); - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'Upload Liga', clubId: club.id, seasonId: season.id }); - const team = await ClubTeam.create({ name: 'Upload Team', clubId: club.id, leagueId: league.id, seasonId: season.id }); - return { club, season, league, team }; -}; - -describe('teamDocumentService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - ensureCleanUploads(); - }); - - afterEach(() => { - ensureCleanUploads(); - }); - - it('lädt Dokumente hoch, ersetzt ältere und löscht sie korrekt', async () => { - const { team } = await createClubTeam(); - - const { filePath } = createTempFile('erste datei'); - const uploadFile = { - path: filePath, - originalname: 'erste.txt', - size: fs.statSync(filePath).size, - mimetype: 'text/plain', - }; - - const doc1 = await TeamDocumentService.uploadDocument(uploadFile, team.id, 'code_list'); - expect(doc1.documentType).toBe('code_list'); - const storedPath1 = doc1.filePath; - expect(fs.existsSync(storedPath1)).toBe(true); - - const { filePath: secondFile } = createTempFile('zweite datei'); - const uploadFile2 = { - path: secondFile, - originalname: 'zweite.txt', - size: fs.statSync(secondFile).size, - mimetype: 'text/plain', - }; - - const doc2 = await TeamDocumentService.uploadDocument(uploadFile2, team.id, 'code_list'); - expect(await TeamDocument.count({ where: { clubTeamId: team.id } })).toBe(1); - expect(fs.existsSync(storedPath1)).toBe(false); - expect(fs.existsSync(doc2.filePath)).toBe(true); - - const deleted = await TeamDocumentService.deleteDocument(doc2.id); - expect(deleted).toBe(true); - expect(fs.existsSync(doc2.filePath)).toBe(false); - expect(await TeamDocument.count()).toBe(0); - }); - - it('liefert Dokumentlisten und Details', async () => { - const { team } = await createClubTeam(); - const { filePath } = createTempFile('inhalt'); - - const uploadFile = { - path: filePath, - originalname: 'dok.txt', - size: fs.statSync(filePath).size, - mimetype: 'text/plain', - }; - - const doc = await TeamDocumentService.uploadDocument(uploadFile, team.id, 'pin_list'); - - const docsForTeam = await TeamDocumentService.getDocumentsByClubTeam(team.id); - expect(docsForTeam).toHaveLength(1); - - const fetched = await TeamDocumentService.getDocumentById(doc.id); - expect(fetched.id).toBe(doc.id); - expect(await TeamDocumentService.getDocumentPath(doc.id)).toBe(fetched.filePath); - }); -}); diff --git a/backend/tests/teamRoutes.test.js b/backend/tests/teamRoutes.test.js deleted file mode 100644 index 62fa6e2..0000000 --- a/backend/tests/teamRoutes.test.js +++ /dev/null @@ -1,90 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import Season from '../models/Season.js'; -import League from '../models/League.js'; -import UserClub from '../models/UserClub.js'; -import Team from '../models/Team.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Team Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('verwaltet Teams eines Clubs (CRUD & Ligen)', async () => { - const { user, credentials } = await registerAndActivate('teamroutes@example.com'); - const token = await loginAndGetToken(credentials); - - const club = await Club.create({ name: 'Route Club' }); - const season = await Season.create({ season: '2025/2026' }); - const league = await League.create({ name: 'Route Liga', clubId: club.id, seasonId: season.id }); - await UserClub.create({ userId: user.id, clubId: club.id, role: 'admin', approved: true }); - - const createResponse = await request(app) - .post(`/api/teams/club/${club.id}`) - .set(authHeaders(token)) - .send({ name: 'Route Team', leagueId: league.id, seasonId: season.id }); - - expect(createResponse.status).toBe(201); - const teamId = createResponse.body.id; - - const listResponse = await request(app) - .get(`/api/teams/club/${club.id}`) - .set(authHeaders(token)) - .query({ seasonid: season.id }); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - - const leaguesResponse = await request(app) - .get(`/api/teams/leagues/${club.id}`) - .set(authHeaders(token)) - .query({ seasonid: season.id }); - - expect(leaguesResponse.status).toBe(200); - expect(leaguesResponse.body).toHaveLength(1); - - const updateResponse = await request(app) - .put(`/api/teams/${teamId}`) - .set(authHeaders(token)) - .send({ name: 'Route Team Updated' }); - - expect(updateResponse.status).toBe(200); - expect(updateResponse.body.name).toBe('Route Team Updated'); - - const deleteResponse = await request(app) - .delete(`/api/teams/${teamId}`) - .set(authHeaders(token)); - - expect(deleteResponse.status).toBe(200); - expect(await Team.count()).toBe(0); - }); -}); diff --git a/backend/tests/teamService.test.js b/backend/tests/teamService.test.js deleted file mode 100644 index f1f9865..0000000 --- a/backend/tests/teamService.test.js +++ /dev/null @@ -1,64 +0,0 @@ -import { describe, it, expect, beforeEach } from 'vitest'; - -import sequelize from '../database.js'; -import '../models/index.js'; - -import teamService from '../services/teamService.js'; -import SeasonService from '../services/seasonService.js'; -import Club from '../models/Club.js'; -import Season from '../models/Season.js'; -import League from '../models/League.js'; -import Team from '../models/Team.js'; - -describe('teamService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('erstellt Teams und weist automatisch die aktuelle Saison zu', async () => { - const club = await Club.create({ name: 'Team Club' }); - const currentSeason = await SeasonService.getOrCreateCurrentSeason(); - - const team = await teamService.createTeam({ name: 'Team A', clubId: club.id }); - - expect(team.name).toBe('Team A'); - expect(team.seasonId).toBe(currentSeason.id); - }); - - it('liefert Teams eines Clubs inklusive Liga und Saison', async () => { - const club = await Club.create({ name: 'Team Club' }); - const season = await Season.create({ season: '2024/2025' }); - const league = await League.create({ name: 'Liga', clubId: club.id, seasonId: season.id }); - await Team.create({ name: 'Team A', clubId: club.id, leagueId: league.id, seasonId: season.id }); - - const teams = await teamService.getAllTeamsByClub(club.id, season.id); - - expect(teams).toHaveLength(1); - expect(teams[0].league.name).toBe('Liga'); - }); - - it('aktualisiert und löscht Teams', async () => { - const club = await Club.create({ name: 'Team Club' }); - const season = await Season.create({ season: '2023/2024' }); - const team = await Team.create({ name: 'Team B', clubId: club.id, seasonId: season.id }); - - const updated = await teamService.updateTeam(team.id, { name: 'Team B Updated' }); - expect(updated).toBe(true); - await team.reload(); - expect(team.name).toBe('Team B Updated'); - - const deleted = await teamService.deleteTeam(team.id); - expect(deleted).toBe(true); - expect(await Team.count()).toBe(0); - }); - - it('liefert Ligen eines Clubs, wobei die aktuelle Saison verwendet wird', async () => { - const club = await Club.create({ name: 'Team Club' }); - const season = await SeasonService.getOrCreateCurrentSeason(); - await League.create({ name: 'Aktuelle Liga', clubId: club.id, seasonId: season.id }); - - const leagues = await teamService.getLeaguesByClub(club.id); - expect(leagues).toHaveLength(1); - expect(leagues[0].name).toBe('Aktuelle Liga'); - }); -}); diff --git a/backend/tests/testApp.js b/backend/tests/testApp.js deleted file mode 100644 index 714e668..0000000 --- a/backend/tests/testApp.js +++ /dev/null @@ -1,64 +0,0 @@ -import express from 'express'; -import { setupRouteAuthMocks } from './utils/routeAuthMocks.js'; -import authRoutes from '../routes/authRoutes.js'; -import permissionRoutes from '../routes/permissionRoutes.js'; -import accidentRoutes from '../routes/accidentRoutes.js'; -import activityRoutes from '../routes/activityRoutes.js'; -import apiLogRoutes from '../routes/apiLogRoutes.js'; -import clubRoutes from '../routes/clubRoutes.js'; -import clubTeamRoutes from '../routes/clubTeamRoutes.js'; -import diaryRoutes from '../routes/diaryRoutes.js'; -import diaryDateActivityRoutes from '../routes/diaryDateActivityRoutes.js'; -import diaryMemberActivityRoutes from '../routes/diaryMemberActivityRoutes.js'; -import diaryNoteRoutes from '../routes/diaryNoteRoutes.js'; -import diaryTagRoutes from '../routes/diaryTagRoutes.js'; -import groupRoutes from '../routes/groupRoutes.js'; -import matchRoutes from '../routes/matchRoutes.js'; -import memberActivityRoutes from '../routes/memberActivityRoutes.js'; -import memberRoutes from '../routes/memberRoutes.js'; -import memberNoteRoutes from '../routes/memberNoteRoutes.js'; -import memberTransferConfigRoutes from '../routes/memberTransferConfigRoutes.js'; -import myTischtennisRoutes from '../routes/myTischtennisRoutes.js'; -import predefinedActivityRoutes from '../routes/predefinedActivityRoutes.js'; -import seasonRoutes from '../routes/seasonRoutes.js'; -import sessionRoutes from '../routes/sessionRoutes.js'; -import teamDocumentRoutes from '../routes/teamDocumentRoutes.js'; -import teamRoutes from '../routes/teamRoutes.js'; -import tournamentRoutes from '../routes/tournamentRoutes.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('/api/logs', apiLogRoutes); -app.use('/api/clubs', clubRoutes); -app.use('/api/clubteam', clubTeamRoutes); -app.use('/api/diary', diaryRoutes); -app.use('/api/diary-date-activities', diaryDateActivityRoutes); -app.use('/api/diary-member-activities', diaryMemberActivityRoutes); -app.use('/api/diary-notes', diaryNoteRoutes); -app.use('/api/diary-tags', diaryTagRoutes); -app.use('/api/groups', groupRoutes); -app.use('/api/matches', matchRoutes); -app.use('/api/member-activities', memberActivityRoutes); -app.use('/api/members', memberRoutes); -app.use('/api/member-notes', memberNoteRoutes); -app.use('/api/member-transfer-config', memberTransferConfigRoutes); -app.use('/api/mytischtennis', myTischtennisRoutes); -app.use('/api/predefined-activities', predefinedActivityRoutes); -app.use('/api/seasons', seasonRoutes); -app.use('/api/session', sessionRoutes); -app.use('/api/team-documents', teamDocumentRoutes); -app.use('/api/teams', teamRoutes); -app.use('/api/tournaments', tournamentRoutes); - -app.use((err, req, res, next) => { - const status = err?.status || err?.statusCode || 500; - const message = err?.message || 'Interner Serverfehler'; - res.status(status).json({ success: false, message, error: message }); -}); - -export default app; diff --git a/backend/tests/testUtils.js b/backend/tests/testUtils.js deleted file mode 100644 index 8c74925..0000000 --- a/backend/tests/testUtils.js +++ /dev/null @@ -1,16 +0,0 @@ -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; -} diff --git a/backend/tests/tournamentRoutes.test.js b/backend/tests/tournamentRoutes.test.js deleted file mode 100644 index acdaee7..0000000 --- a/backend/tests/tournamentRoutes.test.js +++ /dev/null @@ -1,84 +0,0 @@ -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 '../models/index.js'; - -import User from '../models/User.js'; -import Club from '../models/Club.js'; -import Tournament from '../models/Tournament.js'; -import UserClub from '../models/UserClub.js'; -import { createMember } from './utils/factories.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; -}; - -const authHeaders = (token) => ({ - Authorization: `Bearer ${token}`, - authcode: token, -}); - -describe('Tournament Routes', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('legt Turniere an, listet sie und verwaltet Teilnehmer', async () => { - const { user, credentials } = await registerAndActivate('tournament@example.com'); - const token = await loginAndGetToken(credentials); - const club = await Club.create({ name: 'Turnierclub' }); - await UserClub.create({ userId: user.id, clubId: club.id, role: 'admin', approved: true }); - const participant = await createMember(club.id, { - firstName: 'Spieler', - lastName: 'Eins', - email: 'spieler@example.com', - gender: 'male', - }); - - const createResponse = await request(app) - .post('/api/tournaments') - .set(authHeaders(token)) - .send({ clubId: club.id, tournamentName: 'Frühlingsturnier', date: '2025-03-01' }); - - expect(createResponse.status).toBe(201); - const tournamentId = createResponse.body.id; - - const listResponse = await request(app) - .get(`/api/tournaments/${club.id}`) - .set(authHeaders(token)); - - expect(listResponse.status).toBe(200); - expect(listResponse.body).toHaveLength(1); - - const participantResponse = await request(app) - .post('/api/tournaments/participant') - .set(authHeaders(token)) - .send({ clubId: club.id, tournamentId, participant: participant.id }); - - expect(participantResponse.status).toBe(200); - expect(participantResponse.body).toHaveLength(1); - - const participantsList = await request(app) - .post('/api/tournaments/participants') - .set(authHeaders(token)) - .send({ clubId: club.id, tournamentId }); - - expect(participantsList.status).toBe(200); - expect(participantsList.body[0].member.firstName).toBe('Spieler'); - }); -}); diff --git a/backend/tests/tournamentService.test.js b/backend/tests/tournamentService.test.js deleted file mode 100644 index 43a7e46..0000000 --- a/backend/tests/tournamentService.test.js +++ /dev/null @@ -1,706 +0,0 @@ -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -vi.mock('../utils/userUtils.js', async () => { - const actual = await vi.importActual('../utils/userUtils.js'); - return { - ...actual, - checkAccess: vi.fn().mockResolvedValue(true), - }; -}); - -import sequelize from '../database.js'; -import { Op } from 'sequelize'; -import '../models/index.js'; - -import tournamentService from '../services/tournamentService.js'; -import Tournament from '../models/Tournament.js'; -import TournamentGroup from '../models/TournamentGroup.js'; -import TournamentMember from '../models/TournamentMember.js'; -import TournamentMatch from '../models/TournamentMatch.js'; -import TournamentResult from '../models/TournamentResult.js'; -import TournamentStage from '../models/TournamentStage.js'; -import TournamentClass from '../models/TournamentClass.js'; -import Club from '../models/Club.js'; -import { createMember } from './utils/factories.js'; - -describe('tournamentService', () => { - beforeEach(async () => { - await sequelize.sync({ force: true }); - }); - - it('legt Turniere an und verhindert Duplikate', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const date = '2025-09-01'; - - const tournament = await tournamentService.addTournament('token', club.id, 'Herbstturnier', date); - expect(tournament.name).toBe('Herbstturnier'); - - await expect( - tournamentService.addTournament('token', club.id, 'Herbstturnier 2', date) - ).rejects.toThrow('Ein Turnier mit diesem Datum existiert bereits'); - }); - - it('verwaltet Teilnehmer und Gruppen', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const memberA = await createMember(club.id, { - firstName: 'Anna', - lastName: 'A', - email: 'anna@example.com', - gender: 'female', - }); - const memberB = await createMember(club.id, { - firstName: 'Bernd', - lastName: 'B', - email: 'bernd@example.com', - gender: 'male', - }); - - const tournament = await tournamentService.addTournament('token', club.id, 'Wintercup', '2025-12-01'); - - await tournamentService.addParticipant('token', club.id, tournament.id, memberA.id); - await tournamentService.addParticipant('token', club.id, tournament.id, memberB.id); - await expect( - tournamentService.addParticipant('token', club.id, tournament.id, memberA.id) - ).rejects.toThrow('Teilnehmer bereits hinzugefügt'); - - await tournamentService.setModus('token', club.id, tournament.id, 'groups', 1, 1); - await tournamentService.createGroups('token', club.id, tournament.id); - const groupsBeforeFill = await TournamentGroup.findAll({ where: { tournamentId: tournament.id } }); - expect(groupsBeforeFill).toHaveLength(1); - - await tournamentService.fillGroups('token', club.id, tournament.id); - - const membersWithGroups = await TournamentMember.findAll({ where: { tournamentId: tournament.id } }); - expect(membersWithGroups.every((m) => m.groupId !== null)).toBe(true); - - await tournamentService.addMatchResult('token', club.id, tournament.id, (await TournamentMatch.findOne()).id, 1, '11:9'); - const matches = await tournamentService.getTournamentMatches('token', club.id, tournament.id); - expect(matches.length).toBeGreaterThan(0); - }); - - it('erlaubt Teilnehmer ohne Klasse', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const memberA = await createMember(club.id, { - firstName: 'Clara', - lastName: 'C', - email: 'clara@example.com', - gender: 'female', - }); - - const tournament = await tournamentService.addTournament('token', club.id, 'Sommercup', '2025-06-01'); - - // ohne Klasse: legacy-Aufruf (3. Argument = tournamentId) - await tournamentService.addParticipant('token', club.id, tournament.id, memberA.id); - await expect( - tournamentService.addParticipant('token', club.id, tournament.id, memberA.id) - ).rejects.toThrow('Teilnehmer bereits hinzugefügt'); - - const participantsNoClass = await tournamentService.getParticipants('token', club.id, tournament.id, null); - expect(participantsNoClass).toHaveLength(1); - expect(participantsNoClass[0].classId).toBe(null); - expect(participantsNoClass[0].clubMemberId).toBe(memberA.id); - }); - - it('normalisiert numberOfGroups=0 auf mindestens 1 Gruppe', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Gruppen-Test', '2025-07-01'); - - await tournamentService.createGroups('token', club.id, tournament.id, 0); - const groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id } }); - expect(groups).toHaveLength(1); - }); - - it('legt bei numberOfGroups=4 genau 4 Gruppen an (ohne Klassen)', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Gruppen-4er', '2025-08-01'); - - await tournamentService.createGroups('token', club.id, tournament.id, 4); - const groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id } }); - expect(groups).toHaveLength(4); - }); - - it('verteilt bei "zufällig verteilen" möglichst gleichmäßig (Differenz <= 1)', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Fill-Groups-Balanced', '2025-10-01'); - - // 10 Teilnehmer, 4 Gruppen => erwartete Größen: 3/3/2/2 (beliebige Reihenfolge) - const members = []; - for (let i = 0; i < 10; i++) { - // createMember Factory braucht eindeutige Emails - members.push( - await createMember(club.id, { - firstName: `P${i}`, - lastName: 'T', - email: `p${i}@example.com`, - gender: i % 2 === 0 ? 'male' : 'female', - }) - ); - } - - for (const m of members) { - await tournamentService.addParticipant('token', club.id, tournament.id, m.id); - } - - // Seeded-Balancing triggern: markiere mehrere als gesetzt - // (wir testen hier explizit, dass diese Optimierung die Größen-Balance NICHT kaputt machen darf) - const tmRows = await TournamentMember.findAll({ where: { tournamentId: tournament.id } }); - const seededIds = tmRows.slice(0, 5).map(r => r.id); - await TournamentMember.update({ seeded: true }, { where: { id: { [Op.in]: seededIds } } }); - - await tournamentService.setModus('token', club.id, tournament.id, 'groups', 4, 1); - await tournamentService.createGroups('token', club.id, tournament.id, 4); - await tournamentService.fillGroups('token', club.id, tournament.id); - - const groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id } }); - expect(groups).toHaveLength(4); - - const membersWithGroups = await TournamentMember.findAll({ where: { tournamentId: tournament.id } }); - const countsByGroupId = membersWithGroups.reduce((m, tm) => { - m[tm.groupId] = (m[tm.groupId] || 0) + 1; - return m; - }, {}); - - const sizes = groups.map(g => countsByGroupId[g.id] || 0); - const min = Math.min(...sizes); - const max = Math.max(...sizes); - expect(max - min).toBeLessThanOrEqual(1); - expect(sizes.reduce((a, b) => a + b, 0)).toBe(10); - }); - - it('füllt beim KO mit thirdPlace=true das Platz-3-Spiel nach beiden Halbfinals', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'KO-3rd', '2025-11-01'); - - const members = []; - for (let i = 0; i < 4; i++) { - members.push( - await createMember(club.id, { - firstName: `K${i}`, - lastName: 'O', - email: `ko3rd_${i}@example.com`, - gender: i % 2 === 0 ? 'male' : 'female', - }) - ); - } - for (const m of members) { - await tournamentService.addParticipant('token', club.id, tournament.id, m.id); - } - - // Wir gehen den Stage-Flow, damit stageId+groupId gesetzt ist. - await tournamentService.upsertTournamentStages( - 'token', - club.id, - tournament.id, - [ - { index: 1, type: 'groups', name: 'Vorrunde', numberOfGroups: 1 }, - { index: 2, type: 'knockout', name: 'Endrunde', numberOfGroups: null }, - ], - null, - [ - { - fromStageIndex: 1, - toStageIndex: 2, - mode: 'pools', - config: { - pools: [ - { - fromPlaces: [1, 2, 3, 4], - target: { type: 'knockout', singleField: true, thirdPlace: true } - } - ] - } - } - ] - ); - - // Vorrunde-Gruppen+Member-Gruppenzuordnung vorbereiten. - await tournamentService.createGroups('token', club.id, tournament.id, 1); - await tournamentService.fillGroups('token', club.id, tournament.id); - - // Endrunde (KO) erstellen - await tournamentService.advanceTournamentStage('token', club.id, tournament.id, 1, 2); - - const stage2 = await TournamentStage.findOne({ where: { tournamentId: tournament.id, index: 2 } }); - expect(stage2).toBeTruthy(); - - // Third-Place Match muss als Placeholder existieren - const third = await TournamentMatch.findOne({ - where: { tournamentId: tournament.id, stageId: stage2.id, round: 'Spiel um Platz 3' } - }); - expect(third).toBeTruthy(); - expect(third.player1Id).toBe(null); - expect(third.player2Id).toBe(null); - - // Beide Halbfinals abschließen - const semis = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id, stageId: stage2.id }, - order: [['id', 'ASC']] - }); - const semiMatches = semis.filter(m => String(m.round || '').includes('Halbfinale')); - expect(semiMatches.length).toBe(2); - - // Sieger jeweils als Player1 setzen (3 Gewinnsätze simulieren; winningSets default = 3) - for (const sm of semiMatches) { - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 1, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 2, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 3, '11:1'); - } - - const thirdAfter = await TournamentMatch.findOne({ - where: { tournamentId: tournament.id, stageId: stage2.id, round: 'Spiel um Platz 3' } - }); - expect(thirdAfter).toBeTruthy(); - expect(thirdAfter.isActive).toBe(true); - expect(thirdAfter.player1Id).toBeTruthy(); - expect(thirdAfter.player2Id).toBeTruthy(); - expect(thirdAfter.player1Id).not.toBe(thirdAfter.player2Id); - }); - - it('Stage-KO: 3 Gruppen × Plätze 1,2 => 6 Qualifier (keine falschen IDs, keine Duplikate)', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Stage-KO-6', '2025-11-20'); - - // Stages: Vorrunde (Groups) -> Endrunde (KO) - await tournamentService.upsertTournamentStages( - 'token', - club.id, - tournament.id, - [ - { index: 1, type: 'groups', name: 'Vorrunde', numberOfGroups: 3 }, - { index: 2, type: 'knockout', name: 'Endrunde', numberOfGroups: null }, - ], - null, - [ - { - fromStageIndex: 1, - toStageIndex: 2, - mode: 'pools', - config: { - pools: [ - { - fromPlaces: [1, 2], - target: { type: 'knockout', singleField: true, thirdPlace: false }, - }, - ], - }, - }, - ] - ); - - // 3 Gruppen anlegen - await tournamentService.createGroups('token', club.id, tournament.id, 3); - const groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id }, order: [['id', 'ASC']] }); - expect(groups).toHaveLength(3); - - // Je Gruppe 2 Teilnehmer -> insgesamt 6 - const members = []; - for (let i = 0; i < 6; i++) { - members.push( - await createMember(club.id, { - firstName: `S${i}`, - lastName: 'KO', - email: `stage_ko6_${i}@example.com`, - gender: i % 2 === 0 ? 'male' : 'female', - }) - ); - } - // Gruppe 1 - await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: members[0].id, classId: null, groupId: groups[0].id }); - await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: members[1].id, classId: null, groupId: groups[0].id }); - // Gruppe 2 - await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: members[2].id, classId: null, groupId: groups[1].id }); - await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: members[3].id, classId: null, groupId: groups[1].id }); - // Gruppe 3 - await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: members[4].id, classId: null, groupId: groups[2].id }); - await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: members[5].id, classId: null, groupId: groups[2].id }); - - // Gruppenspiele erzeugen+beenden (damit Ranking/Platz 1/2 stabil ist) - // Wir erzeugen minimal pro Gruppe ein 1v1-Match und schließen es ab. - for (const g of groups) { - const [tm1, tm2] = await TournamentMember.findAll({ where: { tournamentId: tournament.id, groupId: g.id }, order: [['id', 'ASC']] }); - const gm = await TournamentMatch.create({ - tournamentId: tournament.id, - round: 'group', - groupId: g.id, - classId: null, - player1Id: tm1.id, - player2Id: tm2.id, - isFinished: true, - isActive: true, - result: '3:0', - }); - await TournamentResult.bulkCreate([ - { matchId: gm.id, pointsPlayer1: 11, pointsPlayer2: 1, setNumber: 1 }, - { matchId: gm.id, pointsPlayer1: 11, pointsPlayer2: 1, setNumber: 2 }, - { matchId: gm.id, pointsPlayer1: 11, pointsPlayer2: 1, setNumber: 3 }, - ]); - } - - // KO-Endrunde erstellen - await tournamentService.advanceTournamentStage('token', club.id, tournament.id, 1, 2); - - const stage2 = await TournamentStage.findOne({ where: { tournamentId: tournament.id, index: 2 } }); - expect(stage2).toBeTruthy(); - - const stage2Matches = await TournamentMatch.findAll({ where: { tournamentId: tournament.id, stageId: stage2.id }, order: [['id', 'ASC']] }); - const round1 = stage2Matches.filter(m => String(m.round || '').includes('Viertelfinale') || String(m.round || '').includes('Achtelfinale') || String(m.round || '').includes('Halbfinale (3)')); - - // Bei 6 Entrants muss ein 8er-Bracket entstehen => 3 Matches in der ersten Runde. - // (Die Byes werden nicht als Matches angelegt.) - expect(round1.length).toBe(3); - for (const m of round1) { - expect(m.player1Id).toBeTruthy(); - expect(m.player2Id).toBeTruthy(); - expect(m.player1Id).not.toBe(m.player2Id); - } - - // Spieler-IDs müssen Member-IDs (clubMemberId) sein, nicht TournamentMember.id - const memberIdSet = new Set(members.map(x => x.id)); - for (const m of round1) { - expect(memberIdSet.has(m.player1Id)).toBe(true); - expect(memberIdSet.has(m.player2Id)).toBe(true); - } - }); - - it('Legacy-KO: legt Platz-3 an und befüllt es nach beiden Halbfinals', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Legacy-KO-3rd', '2025-11-15'); - - // Legacy-KO erzeugt Platz-3 automatisch, sobald ein KO ab Halbfinale gestartet wird. - - // Legacy-startKnockout aktiviert Qualifier-Ermittlung über Gruppen-Logik. - // Dafür erstellen wir 2 Gruppen und spielen je 1 Match fertig. - await tournamentService.setModus('token', club.id, tournament.id, 'groups', 2, 2); - - const members = []; - for (let i = 0; i < 4; i++) { - members.push( - await createMember(club.id, { - firstName: `L${i}`, - lastName: 'KO', - email: `legacy_ko3rd_${i}@example.com`, - gender: i % 2 === 0 ? 'male' : 'female', - }) - ); - } - for (const m of members) { - await tournamentService.addParticipant('token', club.id, tournament.id, m.id); - } - - await tournamentService.createGroups('token', club.id, tournament.id, 2); - await tournamentService.fillGroups('token', club.id, tournament.id); - - // Pro Gruppe 1 Match beenden, damit Qualifier (Platz 1) ermittelt werden können. - // Die Round-Robin-Gruppenspiele werden beim `fillGroups()` bereits angelegt. - const groupMatches = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id, round: 'group' }, - order: [['id', 'ASC']] - }); - expect(groupMatches.length).toBeGreaterThanOrEqual(2); - - // Beende alle Gruppenspiele, damit pro Gruppe die Top-2 zuverlässig bestimmbar sind - for (const gm of groupMatches) { - await tournamentService.addMatchResult('token', club.id, tournament.id, gm.id, 1, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, gm.id, 2, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, gm.id, 3, '11:1'); - } - - // Direkt KO starten (ohne Stages) - await tournamentService.startKnockout('token', club.id, tournament.id); - - const semisAll = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id }, - order: [['id', 'ASC']] - }); - const semiMatches = semisAll.filter(m => String(m.round || '').includes('Halbfinale')); - expect(semiMatches.length).toBe(2); - - // Beide Halbfinals beenden -> Platz-3 muss befüllt werden - for (const sm of semiMatches) { - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 1, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 2, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 3, '11:1'); - } - - const thirdAfter = await TournamentMatch.findOne({ - where: { tournamentId: tournament.id, round: 'Spiel um Platz 3' } - }); - expect(thirdAfter).toBeTruthy(); - expect(thirdAfter.isActive).toBe(true); - expect(thirdAfter.player1Id).toBeTruthy(); - expect(thirdAfter.player2Id).toBeTruthy(); - expect(thirdAfter.player1Id).not.toBe(thirdAfter.player2Id); - }); - - it('Legacy-KO: bei ungerader Qualifier-Zahl wird ein Freilos vergeben (kein Duplikat / kein Self-Match)', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Legacy-KO-Bye', '2025-11-17'); - - // 3 Gruppen, jeweils 1 Spieler -> advancingPerGroup=1 => 3 Qualifier - await tournamentService.setModus('token', club.id, tournament.id, 'groups', 3, 1); - await tournamentService.createGroups('token', club.id, tournament.id, 3); - - const groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id }, order: [['id', 'ASC']] }); - expect(groups).toHaveLength(3); - - const members = []; - for (let i = 0; i < 3; i++) { - members.push( - await createMember(club.id, { - firstName: `B${i}`, - lastName: 'YE', - email: `legacy_bye_${i}@example.com`, - gender: i % 2 === 0 ? 'male' : 'female', - }) - ); - } - - // Je Gruppe genau 1 Teilnehmer, und keine Gruppenspiele nötig (es gibt keine Paarungen) - await TournamentMember.create({ - tournamentId: tournament.id, - clubMemberId: members[0].id, - classId: null, - groupId: groups[0].id, - }); - await TournamentMember.create({ - tournamentId: tournament.id, - clubMemberId: members[1].id, - classId: null, - groupId: groups[1].id, - }); - await TournamentMember.create({ - tournamentId: tournament.id, - clubMemberId: members[2].id, - classId: null, - groupId: groups[2].id, - }); - - // KO starten: Erwartung = genau 1 Match (2 Spieler) + 1 Freilos (ohne extra Match) - await tournamentService.startKnockout('token', club.id, tournament.id); - - const koMatches = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id, round: { [Op.ne]: 'group' } }, - order: [['id', 'ASC']], - }); - - // Bei 3 Qualifiern muss GENAU EIN Halbfinale (3) existieren. - const semi3 = koMatches.filter(m => m.round === 'Halbfinale (3)'); - expect(semi3).toHaveLength(1); - expect(semi3[0].player1Id).toBeTruthy(); - expect(semi3[0].player2Id).toBeTruthy(); - expect(semi3[0].player1Id).not.toBe(semi3[0].player2Id); - - // Self-match darf nirgends vorkommen. - for (const m of koMatches) { - if (m.player1Id && m.player2Id) expect(m.player1Id).not.toBe(m.player2Id); - } - - // Hinweis: Bei 3 Qualifiern wird im Legacy-Flow aktuell ein "Halbfinale (3)" erzeugt. - // Ein automatisches Weitertragen des Freiloses bis in ein fertiges Finale ist nicht Teil dieses Tests. - // Wichtig ist hier die Regression: kein Duplikat und kein Self-Match. - - // Halbfinale beenden (soll keine kaputten Folge-Matches erzeugen) - await tournamentService.addMatchResult('token', club.id, tournament.id, semi3[0].id, 1, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, semi3[0].id, 2, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, semi3[0].id, 3, '11:1'); - - const after = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id, round: { [Op.ne]: 'group' } }, - order: [['id', 'ASC']], - }); - - // Egal ob ein Folge-Match entsteht oder nicht: es darf kein Self-Match geben. - for (const m of after) { - if (m.player1Id && m.player2Id) expect(m.player1Id).not.toBe(m.player2Id); - } - }); - - it('Stage advancement ist klassenisoliert (Zwischen-/Endrunde hängt nur von der jeweiligen Klasse ab)', async () => { - const club = await Club.create({ name: 'Club', accessToken: 'token' }); - const tournament = await Tournament.create({ - clubId: club.id, - name: 'Stages Multi-Class', - date: '2025-12-14', - type: 'groups', - numberOfGroups: 2, - advancingPerGroup: 1, - winningSets: 3, - allowsExternal: false, - }); - - const classA = await TournamentClass.create({ tournamentId: tournament.id, name: 'A' }); - const classB = await TournamentClass.create({ tournamentId: tournament.id, name: 'B' }); - - await tournamentService.upsertTournamentStages( - 'token', - club.id, - tournament.id, - [ - { index: 1, type: 'groups', name: 'Vorrunde', numberOfGroups: 2 }, - { index: 2, type: 'knockout', name: 'Endrunde', numberOfGroups: null }, - ], - null, - [ - { - fromStageIndex: 1, - toStageIndex: 2, - mode: 'pools', - config: { - pools: [ - { fromPlaces: [1], target: { type: 'knockout', singleField: true, thirdPlace: false } }, - ], - }, - }, - ] - ); - - await tournamentService.createGroups('token', club.id, tournament.id, 2); - const groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id }, order: [['id', 'ASC']] }); - expect(groups.length).toBe(2); - - // Klasse A fertig - const memberA1 = await createMember(club.id, { - firstName: 'A1', - lastName: 'Test', - email: 'stage_class_a1@example.com', - gender: 'male', - }); - const memberA2 = await createMember(club.id, { - firstName: 'A2', - lastName: 'Test', - email: 'stage_class_a2@example.com', - gender: 'female', - }); - const a1 = await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: memberA1.id, classId: classA.id, groupId: groups[0].id }); - const a2 = await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: memberA2.id, classId: classA.id, groupId: groups[0].id }); - const aMatch = await TournamentMatch.create({ - tournamentId: tournament.id, - round: 'group', - groupId: groups[0].id, - classId: classA.id, - player1Id: a1.id, - player2Id: a2.id, - isFinished: true, - isActive: true, - result: '3:0', - }); - await TournamentResult.bulkCreate([ - { matchId: aMatch.id, pointsPlayer1: 11, pointsPlayer2: 0, setNumber: 1 }, - { matchId: aMatch.id, pointsPlayer1: 11, pointsPlayer2: 0, setNumber: 2 }, - { matchId: aMatch.id, pointsPlayer1: 11, pointsPlayer2: 0, setNumber: 3 }, - ]); - - // Klasse B unfertig - const memberB1 = await createMember(club.id, { - firstName: 'B1', - lastName: 'Test', - email: 'stage_class_b1@example.com', - gender: 'male', - }); - const memberB2 = await createMember(club.id, { - firstName: 'B2', - lastName: 'Test', - email: 'stage_class_b2@example.com', - gender: 'female', - }); - const b1 = await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: memberB1.id, classId: classB.id, groupId: groups[1].id }); - const b2 = await TournamentMember.create({ tournamentId: tournament.id, clubMemberId: memberB2.id, classId: classB.id, groupId: groups[1].id }); - await TournamentMatch.create({ - tournamentId: tournament.id, - round: 'group', - groupId: groups[1].id, - classId: classB.id, - player1Id: b1.id, - player2Id: b2.id, - isFinished: false, - isActive: true, - result: null, - }); - - await tournamentService.advanceTournamentStage('token', club.id, tournament.id, 1, 2); - const stage2 = await TournamentStage.findOne({ where: { tournamentId: tournament.id, index: 2 } }); - expect(stage2).toBeTruthy(); - - const stage2Matches = await TournamentMatch.findAll({ where: { tournamentId: tournament.id, stageId: stage2.id } }); - expect(stage2Matches.some(m => m.classId === classB.id)).toBe(false); - - // Und es wurden keine Stage2-Gruppen für Klasse B erzeugt. - // (classless Container-Gruppen sind möglich – entscheidend ist, dass Klasse B nicht blockiert/vermengt wird.) - const stage2Groups = await TournamentGroup.findAll({ where: { tournamentId: tournament.id, stageId: stage2.id } }); - expect(stage2Groups.some(g => g.classId === classB.id)).toBe(false); - }); - - it('Legacy-KO: Platz-3 entsteht erst nach beiden Halbfinals (ohne Placeholder)', async () => { - const club = await Club.create({ name: 'Tournament Club' }); - const tournament = await tournamentService.addTournament('token', club.id, 'Legacy-KO-3rd-late', '2025-11-16'); - - // Gruppen nötig, damit startKnockout Qualifier ermitteln kann - await tournamentService.setModus('token', club.id, tournament.id, 'groups', 2, 2); - - const members = []; - for (let i = 0; i < 4; i++) { - members.push( - await createMember(club.id, { - firstName: `LL${i}`, - lastName: 'KO', - email: `legacy_ko3rd_late_${i}@example.com`, - gender: i % 2 === 0 ? 'male' : 'female', - }) - ); - } - for (const m of members) { - await tournamentService.addParticipant('token', club.id, tournament.id, m.id); - } - - await tournamentService.createGroups('token', club.id, tournament.id, 2); - await tournamentService.fillGroups('token', club.id, tournament.id); - - // Alle Gruppenspiele beenden - const groupMatches = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id, round: 'group' }, - order: [['id', 'ASC']] - }); - expect(groupMatches.length).toBeGreaterThanOrEqual(2); - for (const gm of groupMatches) { - await tournamentService.addMatchResult('token', club.id, tournament.id, gm.id, 1, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, gm.id, 2, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, gm.id, 3, '11:1'); - } - - // KO starten - await tournamentService.startKnockout('token', club.id, tournament.id); - - // Vor Halbfinal-Ende darf es kein Platz-3-Spiel geben - const thirdBefore = await TournamentMatch.findOne({ - where: { tournamentId: tournament.id, round: 'Spiel um Platz 3' } - }); - expect(thirdBefore).toBeNull(); - - // Beide Halbfinals beenden -> dabei wird Finale erzeugt. Dabei muss jetzt auch Platz-3 wieder entstehen. - const koMatches = await TournamentMatch.findAll({ - where: { tournamentId: tournament.id }, - order: [['id', 'ASC']] - }); - const semiMatches = koMatches.filter(m => String(m.round || '').includes('Halbfinale')); - expect(semiMatches.length).toBe(2); - - for (const sm of semiMatches) { - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 1, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 2, '11:1'); - await tournamentService.addMatchResult('token', club.id, tournament.id, sm.id, 3, '11:1'); - } - - const thirdAfter = await TournamentMatch.findOne({ - where: { tournamentId: tournament.id, round: 'Spiel um Platz 3' } - }); - expect(thirdAfter).toBeTruthy(); - expect(thirdAfter.player1Id).toBeTruthy(); - expect(thirdAfter.player2Id).toBeTruthy(); - expect(thirdAfter.player1Id).not.toBe(thirdAfter.player2Id); - - const finalAfter = await TournamentMatch.findOne({ - where: { tournamentId: tournament.id, round: 'Finale' } - }); - expect(finalAfter).toBeTruthy(); - }); -}); diff --git a/backend/tests/utils/factories.js b/backend/tests/utils/factories.js deleted file mode 100644 index 151cef1..0000000 --- a/backend/tests/utils/factories.js +++ /dev/null @@ -1,123 +0,0 @@ -import Club from '../../models/Club.js'; -import Member from '../../models/Member.js'; -import User from '../../models/User.js'; -import UserClub from '../../models/UserClub.js'; -import Season from '../../models/Season.js'; -import League from '../../models/League.js'; -import Team from '../../models/Team.js'; -import DiaryDate from '../../models/DiaryDates.js'; - -let uniqueCounter = 0; - -export const nextSuffix = () => { - uniqueCounter += 1; - return uniqueCounter; -}; - -export async function createClub(overrides = {}) { - const suffix = nextSuffix(); - return Club.create({ - name: `Test Club ${suffix}`, - ...overrides, - }); -} - -export function buildMemberData(clubId, overrides = {}) { - const suffix = nextSuffix(); - return { - firstName: `Member${suffix}`, - lastName: `Test${suffix}`, - phone: `0123456${suffix.toString().padStart(3, '0')}`, - street: `Test Street ${suffix}`, - city: 'Test City', - email: `member${suffix}@example.com`, - active: true, - testMembership: false, - picsInInternetAllowed: false, - clubId, - ...overrides, - }; -} - -export async function createMember(clubId, overrides = {}) { - let resolvedClubId = clubId; - if (!resolvedClubId) { - const club = await createClub(); - resolvedClubId = club.id; - } - return Member.create(buildMemberData(resolvedClubId, overrides)); -} - -export async function createDiaryDate(clubId, overrides = {}) { - let resolvedClubId = clubId; - if (!resolvedClubId) { - const club = await createClub(); - resolvedClubId = club.id; - } - return DiaryDate.create({ - date: '2025-01-01', - trainingStart: null, - trainingEnd: null, - clubId: resolvedClubId, - ...overrides, - }); -} - -export async function createUser(overrides = {}) { - const suffix = nextSuffix(); - return User.create({ - email: `user${suffix}@example.com`, - password: 'Secret!123', - isActive: true, - ...overrides, - }); -} - -export async function linkUserToClub(userId, clubId, overrides = {}) { - return UserClub.create({ - userId, - clubId, - role: 'admin', - approved: true, - isOwner: false, - ...overrides, - }); -} - -export async function createUserWithClub(overrides = {}) { - const club = await createClub(overrides.club || {}); - const user = await createUser(overrides.user || {}); - await linkUserToClub(user.id, club.id, overrides.userClub || { isOwner: true }); - return { user, club }; -} - -export async function ensureSeason(overrides = {}) { - const suffix = nextSuffix(); - return Season.create({ - name: `202${suffix}/202${suffix + 1}`, - isCurrent: true, - ...overrides, - }); -} - -export async function createLeague(clubId, overrides = {}) { - const season = overrides.seasonId ? null : await ensureSeason(); - return League.create({ - name: `League ${nextSuffix()}`, - clubId, - seasonId: overrides.seasonId ?? season.id, - ...overrides, - }); -} - -export async function createTeam(clubId, overrides = {}) { - const season = overrides.seasonId ? null : await ensureSeason(); - const league = overrides.leagueId ? null : await createLeague(clubId, { seasonId: overrides.seasonId ?? season.id }); - return Team.create({ - name: `Team ${nextSuffix()}`, - clubId, - leagueId: overrides.leagueId ?? league.id, - seasonId: overrides.seasonId ?? season.id, - ...overrides, - }); -} diff --git a/backend/tests/utils/routeAuthMocks.js b/backend/tests/utils/routeAuthMocks.js deleted file mode 100644 index 78cdf31..0000000 --- a/backend/tests/utils/routeAuthMocks.js +++ /dev/null @@ -1,107 +0,0 @@ -import { vi } from 'vitest'; -import jwt from 'jsonwebtoken'; -import UserClub from '../../models/UserClub.js'; - -function resolveUserFromToken(token) { - if (!token || token === 'forbidden') { - const error = new Error('noaccess'); - error.status = 403; - throw error; - } - - try { - const secret = process.env.JWT_SECRET || 'test-secret'; - const payload = jwt.verify(token, secret); - return { id: payload.userId || 1 }; - } catch (error) { - const fallbackError = new Error('noaccess'); - fallbackError.status = 403; - throw fallbackError; - } -} - -async function ensureClubAccess(token, clubId) { - const user = resolveUserFromToken(token); - const normalizedClubId = Number(clubId); - - if (Number.isNaN(normalizedClubId)) { - const error = new Error('noaccess'); - error.status = 403; - throw error; - } - - const membership = await UserClub.findOne({ - where: { - userId: user.id, - clubId: normalizedClubId, - approved: true, - }, - }); - - if (!membership) { - const error = new Error('noaccess'); - error.status = 403; - throw error; - } - - return user; -} - -let mocksRegistered = false; - -export function setupRouteAuthMocks() { - if (mocksRegistered) { - return; - } - - vi.mock('../../middleware/authMiddleware.js', () => ({ - authenticate: (req, res, next) => { - const headerToken = req.headers.authorization?.split(' ')[1] || req.headers.authcode; - if (!headerToken) { - return res.status(401).json({ error: 'Unauthenticated' }); - } - try { - const user = resolveUserFromToken(headerToken); - req.user = user; - req.headers.authcode = headerToken; - } catch (error) { - return res.status(error.status || 401).json({ error: error.message || 'Unauthenticated' }); - } - next(); - }, - })); - - vi.mock('../../middleware/authorizationMiddleware.js', () => ({ - authorize: () => (req, res, next) => { - if (req.headers['x-allow-authorization'] === 'false') { - return res.status(403).json({ error: 'Forbidden' }); - } - next(); - }, - })); - - vi.mock('../../utils/userUtils.js', () => ({ - checkAccess: vi.fn().mockImplementation(async (token, clubId) => { - await ensureClubAccess(token, clubId); - return true; - }), - checkGlobalAccess: vi.fn().mockImplementation(async (token) => resolveUserFromToken(token ?? '')), - getUserByToken: vi.fn().mockImplementation((token) => resolveUserFromToken(token)), - hasUserClubAccess: vi.fn().mockImplementation(async (userId, clubId) => { - const normalizedClubId = Number(clubId); - if (Number.isNaN(normalizedClubId)) { - return false; - } - const membership = await UserClub.findOne({ - where: { - userId, - clubId: normalizedClubId, - approved: true, - }, - }); - return Boolean(membership); - }), - })); - - mocksRegistered = true; -}