diff --git a/backend/server.js b/backend/server.js index b94d2c7..779b50e 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,6 +1,6 @@ // 1. Load the environment variables FIRST require('dotenv').config(); -console.log("DEBUG -> JWT_SECRET VALUE IS:", process.env.JWT_SECRET); + // 2. NOW load your app, database, and models const app = require('./src/app'); const sequelize = require('./src/config/database'); @@ -21,7 +21,18 @@ async function startServer() { }); } catch (error) { console.error('Failed to start server:', error); + process.exit(1); } } +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); +}); + +process.on('uncaughtException', (error) => { + console.error('Uncaught Exception:', error); + process.exit(1); +}); + startServer(); \ No newline at end of file diff --git a/backend/src/__tests__/auth.controller.test.js b/backend/src/__tests__/auth.controller.test.js index 46bf234..cb1097d 100644 --- a/backend/src/__tests__/auth.controller.test.js +++ b/backend/src/__tests__/auth.controller.test.js @@ -91,7 +91,7 @@ describe('register', () => { await register(req, res); expect(res.status).toHaveBeenCalledWith(500); expect(res.json).toHaveBeenCalledWith( - expect.objectContaining({ success: false, message: 'Server error' }) + expect.objectContaining({ success: false, message: 'An unexpected error occurred during registration' }) ); }); }); @@ -99,12 +99,23 @@ describe('register', () => { // ── LOGIN ─────────────────────────────────────────────────────────────────── describe('login', () => { + const originalJwtSecret = process.env.JWT_SECRET; + const originalJwtExpiresIn = process.env.JWT_EXPIRES_IN; + beforeEach(() => { jest.clearAllMocks(); process.env.JWT_SECRET = 'test-secret'; process.env.JWT_EXPIRES_IN = '1h'; }); + afterAll(() => { + if (originalJwtSecret === undefined) delete process.env.JWT_SECRET; + else process.env.JWT_SECRET = originalJwtSecret; + + if (originalJwtExpiresIn === undefined) delete process.env.JWT_EXPIRES_IN; + else process.env.JWT_EXPIRES_IN = originalJwtExpiresIn; + }); + it('returns 400 when email is missing', async () => { const { req, res } = mockReqRes({ password: '123456' }); await login(req, res); @@ -180,6 +191,22 @@ describe('login', () => { ); }); + it('returns 500 when JWT_SECRET is not configured', async () => { + delete process.env.JWT_SECRET; + const fakeUser = { id: '1', name: 'Test', email: 'a@b.com', password: 'hashed' }; + User.findOne.mockResolvedValue(fakeUser); + bcrypt.compare.mockResolvedValue(true); + + const { req, res } = mockReqRes({ email: 'a@b.com', password: '123456' }); + await login(req, res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ success: false, message: 'Server configuration error' }) + ); + expect(jwt.sign).not.toHaveBeenCalled(); + }); + it('returns 500 on unexpected error', async () => { User.findOne.mockRejectedValue(new Error('DB down')); const { req, res } = mockReqRes({ email: 'a@b.com', password: '123456' }); diff --git a/backend/src/app.js b/backend/src/app.js index b69bff4..df06526 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -21,4 +21,28 @@ app.get('/api/health', (req, res) => { }); }); +// 404 handler for unmatched routes +app.use((req, res) => { + res.status(404).json({ + success: false, + message: `Route ${req.method} ${req.originalUrl} not found`, + }); +}); + +// Global error-handling middleware +app.use((err, req, res, _next) => { + if (err.type === 'entity.parse.failed') { + return res.status(400).json({ + success: false, + message: 'Malformed JSON in request body', + }); + } + + console.error('Unhandled error:', err); + res.status(err.status || 500).json({ + success: false, + message: err.message || 'Internal server error', + }); +}); + module.exports = app; \ No newline at end of file diff --git a/backend/src/config/database.js b/backend/src/config/database.js index d0b3810..4772da0 100644 --- a/backend/src/config/database.js +++ b/backend/src/config/database.js @@ -1,6 +1,14 @@ const { Sequelize } = require('sequelize'); require('dotenv').config(); +const requiredVars = ['DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST', 'DB_PORT']; +const missing = requiredVars.filter((key) => !process.env[key]); +if (missing.length > 0) { + throw new Error( + `Missing required database environment variables: ${missing.join(', ')}` + ); +} + const sequelize = new Sequelize( process.env.DB_NAME, process.env.DB_USER, diff --git a/backend/src/controllers/auth.controller.js b/backend/src/controllers/auth.controller.js index 62e0f83..239ded2 100644 --- a/backend/src/controllers/auth.controller.js +++ b/backend/src/controllers/auth.controller.js @@ -49,10 +49,26 @@ if (!name || !email || !password) { }, }); } catch (error) { + if (error.name === 'SequelizeValidationError') { + const messages = error.errors.map((e) => e.message); + return res.status(400).json({ + success: false, + message: 'Validation failed', + errors: messages, + }); + } + + if (error.name === 'SequelizeUniqueConstraintError') { + return res.status(409).json({ + success: false, + message: 'Email already registered', + }); + } + console.error('Register error:', error); return res.status(500).json({ success: false, - message: 'Server error', + message: 'An unexpected error occurred during registration', }); } }; @@ -91,6 +107,14 @@ const login = async (req, res) => { } // 4. Generate JWT token + if (!process.env.JWT_SECRET) { + console.error('JWT_SECRET is not configured'); + return res.status(500).json({ + success: false, + message: 'Server configuration error', + }); + } + const token = jwt.sign( { id: user.id, email: user.email }, process.env.JWT_SECRET,