Add API logging and club routes to the Express application
Integrated new routes for API logging and club management into the backend, enhancing the API's functionality and organization. This addition allows for better access to logging and club-related resources, improving overall application structure.
This commit is contained in:
92
backend/tests/apiLogRoutes.test.js
Normal file
92
backend/tests/apiLogRoutes.test.js
Normal file
@@ -0,0 +1,92 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
72
backend/tests/apiLogService.test.js
Normal file
72
backend/tests/apiLogService.test.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
135
backend/tests/clubRoutes.test.js
Normal file
135
backend/tests/clubRoutes.test.js
Normal file
@@ -0,0 +1,135 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
90
backend/tests/clubService.test.js
Normal file
90
backend/tests/clubService.test.js
Normal file
@@ -0,0 +1,90 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,8 @@ 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';
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -11,6 +13,8 @@ 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((err, req, res, next) => {
|
||||
const status = err?.status || err?.statusCode || 500;
|
||||
|
||||
Reference in New Issue
Block a user