fix: resolve test failures in CI pipeline

- Add jest.setup.js with JWT_SECRET for test environment
- Update jest.config.js with setupFiles and increased timeout
- Fix auth middleware to return 401 (not 403) for invalid JWT tokens
- Fix errorHandler to return 'message' instead of 'error' in response
- Fix validate middleware to properly detect Zod errors in ESM
- Remove unused 'pool' import in middleware tests (lint fix)
- Update middleware tests to check next() calls with AppError

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
soufiane 2025-11-27 12:05:00 +01:00
parent 614abeb196
commit 74a7f387c5
6 changed files with 46 additions and 15 deletions

View File

@ -2,6 +2,9 @@ export default {
// Use Node's experimental ESM support // Use Node's experimental ESM support
testEnvironment: 'node', testEnvironment: 'node',
// Setup files to run before tests
setupFiles: ['./jest.setup.js'],
// Transform ES modules // Transform ES modules
transform: {}, transform: {},
@ -31,4 +34,7 @@ export default {
// Verbose output // Verbose output
verbose: true, verbose: true,
// Test timeout (increase for database operations)
testTimeout: 10000,
}; };

13
jest.setup.js Normal file
View File

@ -0,0 +1,13 @@
/**
* Jest setup file - configure test environment
*/
// Set test environment variables
process.env.NODE_ENV = 'test';
process.env.JWT_SECRET = 'test-jwt-secret-key-for-testing-purposes-only';
process.env.JWT_EXPIRES_IN = '1h';
process.env.PORT = '3001';
// Disable console logs during tests (optional)
// console.log = jest.fn();
// console.error = jest.fn();

View File

@ -51,10 +51,10 @@ export const authenticateToken = async (req, res, next) => {
next(); next();
} catch (error) { } catch (error) {
if (error.name === 'JsonWebTokenError') { if (error.name === 'JsonWebTokenError') {
return next(new AppError('Token invalide', 403)); return next(new AppError('Token invalide', 401));
} }
if (error.name === 'TokenExpiredError') { if (error.name === 'TokenExpiredError') {
return next(new AppError('Token expiré', 403)); return next(new AppError('Token expiré', 401));
} }
return next(error); return next(error);
} }

View File

@ -52,7 +52,7 @@ export const errorHandler = (err, req, res, _next) => {
res.status(error.statusCode || 500).json({ res.status(error.statusCode || 500).json({
success: false, success: false,
error: error.message || 'Erreur serveur', message: error.message || 'Erreur serveur',
...(process.env.NODE_ENV === 'development' && { stack: err.stack }), ...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
}); });
}; };

View File

@ -11,16 +11,17 @@ export const validate = (schema) => async (req, res, next) => {
}); });
next(); next();
} catch (error) { } catch (error) {
if (error.errors) { // Check for Zod validation errors (works with ESM)
if (error.issues && Array.isArray(error.issues)) {
// Erreurs de validation Zod // Erreurs de validation Zod
const errorMessages = error.errors.map((err) => ({ const errorMessages = error.issues.map((err) => ({
field: err.path.join('.'), field: err.path.join('.'),
message: err.message, message: err.message,
})); }));
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
error: 'Erreur de validation', message: 'Erreur de validation',
details: errorMessages, details: errorMessages,
}); });
} }

View File

@ -10,7 +10,6 @@ jest.unstable_mockModule('../../db.js', () => ({
}, },
})); }));
const { pool } = await import('../../db.js');
const { authenticateToken, authorizeRoles, optionalAuth } = await import('../../src/middleware/auth.js'); const { authenticateToken, authorizeRoles, optionalAuth } = await import('../../src/middleware/auth.js');
const { AppError, errorHandler, asyncHandler } = await import('../../src/middleware/errorHandler.js'); const { AppError, errorHandler, asyncHandler } = await import('../../src/middleware/errorHandler.js');
const { validate } = await import('../../src/middleware/validate.js'); const { validate } = await import('../../src/middleware/validate.js');
@ -37,13 +36,12 @@ describe('Auth Middleware', () => {
it('should return 401 if no token provided', async () => { it('should return 401 if no token provided', async () => {
await authenticateToken(mockReq, mockRes, mockNext); await authenticateToken(mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(401); expect(mockNext).toHaveBeenCalledWith(
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
success: false, statusCode: 401,
isOperational: true,
}) })
); );
expect(mockNext).not.toHaveBeenCalled();
}); });
it('should return 401 for invalid token', async () => { it('should return 401 for invalid token', async () => {
@ -51,8 +49,12 @@ describe('Auth Middleware', () => {
await authenticateToken(mockReq, mockRes, mockNext); await authenticateToken(mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(401); expect(mockNext).toHaveBeenCalledWith(
expect(mockNext).not.toHaveBeenCalled(); expect.objectContaining({
statusCode: 401,
isOperational: true,
})
);
}); });
}); });
@ -72,8 +74,12 @@ describe('Auth Middleware', () => {
middleware(mockReq, mockRes, mockNext); middleware(mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(403); expect(mockNext).toHaveBeenCalledWith(
expect(mockNext).not.toHaveBeenCalled(); expect.objectContaining({
statusCode: 403,
isOperational: true,
})
);
}); });
}); });
@ -132,6 +138,11 @@ describe('Error Handler Middleware', () => {
errorHandler(error, mockReq, mockRes, mockNext); errorHandler(error, mockReq, mockRes, mockNext);
expect(mockRes.status).toHaveBeenCalledWith(500); expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({
success: false,
})
);
}); });
}); });