Refactor database configuration and enhance error handling in authentication services

Updated the database configuration to centralize settings and improve maintainability. Enhanced error handling in the authentication service to provide clearer and more specific error messages for various failure scenarios, including registration, activation, and login processes. Additionally, added new dependencies for testing and SQLite support in the package.json file.
This commit is contained in:
Torsten Schulz (local)
2025-11-10 16:54:49 +01:00
parent 620b065ac8
commit 3f1018ef93
14 changed files with 716 additions and 11 deletions

View File

@@ -7,7 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config({ path: path.resolve(__dirname, '.env') });
export const development = {
const baseConfig = {
username: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || 'hitomisan',
database: process.env.DB_NAME || 'trainingdiary',
@@ -18,5 +18,12 @@ export const development = {
underscored: true,
underscoredAll: true,
},
logging: false,
};
if (baseConfig.dialect === 'sqlite') {
baseConfig.storage = process.env.DB_STORAGE || ':memory:';
}
export const development = baseConfig;

View File

@@ -10,7 +10,8 @@ const sequelize = new Sequelize(
host: development.host,
dialect: development.dialect,
define: development.define,
logging: false, // SQL-Logging deaktivieren
logging: development.logging ?? false,
storage: development.storage,
}
);

View File

@@ -7,7 +7,8 @@
"postinstall": "cd ../frontend && npm install && npm run build",
"dev": "nodemon server.js",
"cleanup:usertoken": "node ./scripts/cleanupUserTokenKeys.js",
"cleanup:indexes": "node ./scripts/cleanupAllIndexes.js"
"cleanup:indexes": "node ./scripts/cleanupAllIndexes.js",
"test": "cross-env NODE_ENV=test vitest run --runInBand"
},
"keywords": [],
"author": "",
@@ -34,7 +35,11 @@
"sharp": "^0.33.5"
},
"devDependencies": {
"cross-env": "^7.0.3",
"nodemon": "^3.1.4",
"supertest": "^7.1.1",
"sqlite3": "^5.1.7",
"vitest": "^1.6.0",
"vue-eslint-parser": "9.4.3"
}
}

View File

@@ -13,13 +13,24 @@ const register = async (email, password) => {
return user;
} catch (error) {
devLog(error);
return null;
if (error.name === 'SequelizeUniqueConstraintError') {
const err = new Error('E-Mail-Adresse wird bereits verwendet');
err.status = 409;
throw err;
}
const err = new Error('Registrierung fehlgeschlagen');
err.status = 400;
throw err;
}
};
const activateUser = async (activationCode) => {
const user = await User.findOne({ where: { activationCode } });
if (!user) throw new Error('Invalid activation code');
if (!user) {
const err = new Error('Aktivierungscode ungültig');
err.status = 404;
throw err;
}
user.isActive = true;
user.activationCode = null;
await user.save();
@@ -28,11 +39,21 @@ const activateUser = async (activationCode) => {
const login = async (email, password) => {
if (!email || !password) {
throw { status: 400, message: 'Email und Passwort sind erforderlich.' };
const err = new Error('Email und Passwort sind erforderlich.');
err.status = 400;
throw err;
}
const user = await User.findOne({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
throw { status: 401, message: 'Ungültige Anmeldedaten' };
const validPassword = user && await bcrypt.compare(password, user.password);
if (!validPassword) {
const err = new Error('Ungültige Anmeldedaten');
err.status = 401;
throw err;
}
if (!user.isActive) {
const err = new Error('Account wurde noch nicht aktiviert');
err.status = 403;
throw err;
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '3h' });
await UserToken.create({
@@ -45,7 +66,9 @@ const login = async (email, password) => {
const logout = async (token) => {
if (!token) {
throw { status: 400, message: 'Token fehlt' };
const err = new Error('Token fehlt');
err.status = 400;
throw err;
}
await UserToken.destroy({ where: { token } });
return { message: 'Logout erfolgreich' };

View File

@@ -0,0 +1,60 @@
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();
});
});

View File

@@ -0,0 +1,161 @@
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);
});
});

View File

@@ -0,0 +1,99 @@
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 });
});
});

View File

@@ -0,0 +1,68 @@
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();
});
});

View File

@@ -0,0 +1,109 @@
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);
});
});

View File

@@ -0,0 +1,128 @@
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);
});
});

View File

@@ -0,0 +1,13 @@
import { afterAll } from 'vitest';
import sequelize from '../database.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';
afterAll(async () => {
await sequelize.close();
});

17
backend/tests/testApp.js Normal file
View File

@@ -0,0 +1,17 @@
import express from 'express';
import authRoutes from '../routes/authRoutes.js';
import permissionRoutes from '../routes/permissionRoutes.js';
const app = express();
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/permissions', permissionRoutes);
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;

14
backend/vitest.config.js Normal file
View File

@@ -0,0 +1,14 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./tests/setupTestEnv.js'],
include: ['tests/**/*.test.js'],
coverage: {
reporter: ['text', 'html'],
exclude: ['migrations/**', 'uploads/**', 'scripts/**'],
},
},
});

View File

@@ -346,7 +346,7 @@ import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
import InfoDialog from '../components/InfoDialog.vue';
import ConfirmDialog from '../components/ConfirmDialog.vue';
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage } from '../utils/dialogUtils.js';
import { buildInfoConfig, buildConfirmConfig } from '../utils/dialogUtils.js';
export default {
name: 'TeamManagementView',
@@ -720,7 +720,7 @@ export default {
await loadTeamDocuments();
} catch (error) {
console.error('Fehler beim Hochladen und Parsen der Datei:', error);
const message = safeErrorMessage(error, 'Fehler beim Hochladen und Parsen der Datei.');
const message = getSafeErrorMessage(error, 'Fehler beim Hochladen und Parsen der Datei.');
await showInfo('Fehler', message, '', 'error');
} finally {
parsingInProgress.value = false;