const request = require('supertest'); const express = require('express'); // Mock dependencies jest.mock('../../../models', () => ({ Feedback: { create: jest.fn(), }, User: { findByPk: jest.fn(), }, })); jest.mock('../../../middleware/auth', () => ({ authenticateToken: (req, res, next) => { req.user = { id: 'user-123', email: 'test@example.com' }; next(); }, })); jest.mock('../../../middleware/validation', () => ({ validateFeedback: (req, res, next) => next(), sanitizeInput: (req, res, next) => next(), })); jest.mock('../../../services/email', () => ({ feedback: { sendFeedbackConfirmation: jest.fn(), sendFeedbackNotificationToAdmin: jest.fn(), }, })); jest.mock('../../../utils/logger', () => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), withRequestId: jest.fn(() => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), })), })); const { Feedback } = require('../../../models'); const emailServices = require('../../../services/email'); const feedbackRoutes = require('../../../routes/feedback'); describe('Feedback Routes', () => { let app; beforeEach(() => { app = express(); app.use(express.json()); app.use('/feedback', feedbackRoutes); // Add error handler app.use((err, req, res, next) => { res.status(500).json({ error: err.message }); }); jest.clearAllMocks(); }); describe('POST /feedback', () => { it('should create feedback successfully', async () => { const mockFeedback = { id: 'feedback-123', userId: 'user-123', feedbackText: 'Great app!', url: 'https://example.com/page', userAgent: 'Mozilla/5.0', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); const response = await request(app) .post('/feedback') .set('User-Agent', 'Mozilla/5.0') .send({ feedbackText: 'Great app!', url: 'https://example.com/page', }); expect(response.status).toBe(201); expect(response.body.id).toBe('feedback-123'); expect(response.body.feedbackText).toBe('Great app!'); expect(Feedback.create).toHaveBeenCalledWith({ userId: 'user-123', feedbackText: 'Great app!', url: 'https://example.com/page', userAgent: 'Mozilla/5.0', }); }); it('should send confirmation email to user', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(emailServices.feedback.sendFeedbackConfirmation).toHaveBeenCalledWith( { id: 'user-123', email: 'test@example.com' }, mockFeedback ); }); it('should send notification email to admin', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(emailServices.feedback.sendFeedbackNotificationToAdmin).toHaveBeenCalledWith( { id: 'user-123', email: 'test@example.com' }, mockFeedback ); }); it('should succeed even if confirmation email fails', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockRejectedValue(new Error('Email failed')); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); const response = await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(response.status).toBe(201); }); it('should succeed even if admin notification email fails', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockRejectedValue(new Error('Email failed')); const response = await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(response.status).toBe(201); }); it('should handle feedback with null url', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', url: null, }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); const response = await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(response.status).toBe(201); expect(Feedback.create).toHaveBeenCalledWith( expect.objectContaining({ url: null, }) ); }); it('should capture user agent from headers', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', userAgent: 'CustomUserAgent/1.0', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); await request(app) .post('/feedback') .set('User-Agent', 'CustomUserAgent/1.0') .send({ feedbackText: 'Great app!' }); expect(Feedback.create).toHaveBeenCalledWith( expect.objectContaining({ userAgent: 'CustomUserAgent/1.0', }) ); }); it('should handle missing user agent', async () => { const mockFeedback = { id: 'feedback-123', feedbackText: 'Great app!', }; Feedback.create.mockResolvedValue(mockFeedback); emailServices.feedback.sendFeedbackConfirmation.mockResolvedValue(); emailServices.feedback.sendFeedbackNotificationToAdmin.mockResolvedValue(); const response = await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(response.status).toBe(201); }); it('should return 500 when database error occurs', async () => { Feedback.create.mockRejectedValue(new Error('Database error')); const response = await request(app) .post('/feedback') .send({ feedbackText: 'Great app!' }); expect(response.status).toBe(500); }); }); });