Add new API routes for season, session, team, and tournament management
Integrated additional routes into the Express application for managing seasons, sessions, teams, team documents, and tournaments. This enhancement improves the API's structure and expands its capabilities, allowing for better organization and access to related resources.
This commit is contained in:
3027
backend/node_modules/.package-lock.json
generated
vendored
3027
backend/node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
2988
backend/package-lock.json
generated
2988
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
94
backend/tests/seasonRoutes.test.js
Normal file
94
backend/tests/seasonRoutes.test.js
Normal file
@@ -0,0 +1,94 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
50
backend/tests/seasonService.test.js
Normal file
50
backend/tests/seasonService.test.js
Normal file
@@ -0,0 +1,50 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
98
backend/tests/sessionRoutes.test.js
Normal file
98
backend/tests/sessionRoutes.test.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import request from 'supertest';
|
||||
|
||||
vi.mock('../services/emailService.js', () => ({
|
||||
sendActivationEmail: vi.fn().mockResolvedValue(),
|
||||
}));
|
||||
|
||||
const mockScheduler = {
|
||||
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 = {
|
||||
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();
|
||||
});
|
||||
});
|
||||
104
backend/tests/teamDocumentRoutes.test.js
Normal file
104
backend/tests/teamDocumentRoutes.test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
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 uploadResponse = await request(app)
|
||||
.post(`/api/team-documents/club-team/${team.id}/upload`)
|
||||
.set(authHeaders(token))
|
||||
.field('documentType', 'code_list')
|
||||
.attach('document', Buffer.from('Sa. 06.09.2025 10:00 Harheimer TC II SG Teststadt III ABC123DEF456'), 'codes.txt');
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
102
backend/tests/teamDocumentService.test.js
Normal file
102
backend/tests/teamDocumentService.test.js
Normal file
@@ -0,0 +1,102 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
90
backend/tests/teamRoutes.test.js
Normal file
90
backend/tests/teamRoutes.test.js
Normal file
@@ -0,0 +1,90 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
64
backend/tests/teamService.test.js
Normal file
64
backend/tests/teamService.test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,11 @@ 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 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();
|
||||
|
||||
@@ -41,6 +46,11 @@ 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/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;
|
||||
|
||||
97
backend/tests/tournamentRoutes.test.js
Normal file
97
backend/tests/tournamentRoutes.test.js
Normal file
@@ -0,0 +1,97 @@
|
||||
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 Member from '../models/Member.js';
|
||||
import Tournament from '../models/Tournament.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,
|
||||
});
|
||||
|
||||
const createMember = async (clubId, overrides = {}) => {
|
||||
return Member.create({
|
||||
firstName: 'Spieler',
|
||||
lastName: 'Eins',
|
||||
phone: '123456',
|
||||
street: 'Straße 1',
|
||||
city: 'Stadt',
|
||||
postalCode: '12345',
|
||||
email: 'spieler@example.com',
|
||||
clubId,
|
||||
active: true,
|
||||
testMembership: false,
|
||||
picsInInternetAllowed: false,
|
||||
gender: 'male',
|
||||
...overrides,
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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.email).toBe('spieler@example.com');
|
||||
});
|
||||
});
|
||||
92
backend/tests/tournamentService.test.js
Normal file
92
backend/tests/tournamentService.test.js
Normal file
@@ -0,0 +1,92 @@
|
||||
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 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 Club from '../models/Club.js';
|
||||
import Member from '../models/Member.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 Member.create({
|
||||
firstName: 'Anna',
|
||||
lastName: 'A',
|
||||
phone: '123',
|
||||
street: 'Straße 1',
|
||||
city: 'Stadt',
|
||||
postalCode: '12345',
|
||||
email: 'anna@example.com',
|
||||
clubId: club.id,
|
||||
active: true,
|
||||
testMembership: false,
|
||||
picsInInternetAllowed: false,
|
||||
gender: 'female',
|
||||
});
|
||||
const memberB = await Member.create({
|
||||
firstName: 'Bernd',
|
||||
lastName: 'B',
|
||||
phone: '456',
|
||||
street: 'Straße 2',
|
||||
city: 'Stadt',
|
||||
postalCode: '54321',
|
||||
email: 'bernd@example.com',
|
||||
clubId: club.id,
|
||||
active: true,
|
||||
testMembership: false,
|
||||
picsInInternetAllowed: false,
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user