yatra-x-feedback / __tests__ /feedback.controller.test.js
imkrish's picture
Upload folder using huggingface_hub
d82af56 verified
Raw History Blame Contribute Delete
10.7 kB
'use strict';
/**
* Feedback Controller Unit Tests
* Tests for feedback event endpoints
*/
// Mock dependencies before requiring the controller
const mockPool = {
query: jest.fn(),
connect: jest.fn(() => ({
query: jest.fn(),
release: jest.fn()
}))
};
jest.mock('../src/db', () => ({
getPool: jest.fn(() => mockPool)
}));
jest.mock('uuid', () => ({
v4: jest.fn(() => 'mock-uuid-1234')
}));
const feedbackController = require('../src/controllers/feedback.controller');
const { getPool } = require('../src/db');
describe('Feedback Controller', () => {
let mockReq;
let mockRes;
let mockPool;
beforeEach(() => {
mockReq = {
body: {},
query: {},
params: {}
};
mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis()
};
mockPool = getPool();
jest.clearAllMocks();
});
describe('createFeedbackEvent', () => {
it('should return 400 if event_type is missing', async () => {
mockReq.body = { session_id: 'session-1' };
await feedbackController.createFeedbackEvent(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({
error: { message: 'event_type is required' }
});
});
it('should return 400 for invalid event_type', async () => {
mockReq.body = { event_type: 'invalid_type' };
await feedbackController.createFeedbackEvent(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({
error: expect.objectContaining({
message: expect.stringContaining('Invalid event_type')
})
})
);
});
it('should accept all valid event types', async () => {
const validEventTypes = [
'plan_saved', 'plan_completed', 'rating_positive',
'poi_added', 'plan_abandoned', 'poi_removed',
'rating_negative', 'poi_reordered', 'poi_swapped', 'session_start'
];
for (const eventType of validEventTypes) {
mockReq.body = { event_type: eventType };
mockPool.query.mockResolvedValueOnce({});
await feedbackController.createFeedbackEvent(mockReq, mockRes);
// Reset for next iteration
jest.clearAllMocks();
mockPool = getPool();
}
});
it('should create a feedback event successfully', async () => {
mockReq.body = {
event_type: 'plan_saved',
session_id: 'session-123',
user_hash: 'user-hash-456',
context: { destination: 'Ranchi' },
itinerary_snapshot: [{ poi_id: 'poi-1', name: 'Hundru Falls' }],
modifications: [],
reward: 1.5
};
mockPool.query.mockResolvedValueOnce({});
await feedbackController.createFeedbackEvent(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
expect(mockRes.json).toHaveBeenCalledWith({
data: expect.objectContaining({
id: 'mock-uuid-1234',
event_type: 'plan_saved',
session_id: 'session-123',
reward: 1.5
}),
message: 'Feedback event recorded successfully'
});
});
it('should return 503 if database is not available', async () => {
mockReq.body = { event_type: 'plan_saved' };
// Override getPool to return null
require('../src/db').getPool.mockReturnValueOnce(null);
await feedbackController.createFeedbackEvent(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(503);
expect(mockRes.json).toHaveBeenCalledWith({
error: { message: 'Database not available' }
});
});
it('should return 500 on database error', async () => {
mockReq.body = { event_type: 'plan_saved' };
mockPool.query.mockRejectedValueOnce(new Error('DB connection failed'));
await feedbackController.createFeedbackEvent(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
error: { message: 'Failed to record feedback event' }
});
});
it('should use default values for optional fields', async () => {
mockReq.body = { event_type: 'session_start' };
mockPool.query.mockResolvedValueOnce({});
await feedbackController.createFeedbackEvent(mockReq, mockRes);
expect(mockPool.query).toHaveBeenCalledWith(
expect.any(String),
expect.arrayContaining([
'mock-uuid-1234',
'session_start',
null, // session_id
null, // user_hash
'{}', // context
'[]', // itinerary_snapshot
'[]', // modifications
0 // reward
])
);
});
});
describe('listFeedbackEvents', () => {
it('should list all feedback events with default pagination', async () => {
const mockEvents = [
{ id: '1', event_type: 'plan_saved' },
{ id: '2', event_type: 'poi_added' }
];
mockPool.query.mockResolvedValueOnce({ rows: mockEvents });
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith({ data: mockEvents });
});
it('should filter by event_type', async () => {
mockReq.query = { event_type: 'plan_saved' };
mockPool.query.mockResolvedValueOnce({ rows: [] });
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('event_type = $'),
expect.arrayContaining(['plan_saved'])
);
});
it('should filter by session_id', async () => {
mockReq.query = { session_id: 'session-123' };
mockPool.query.mockResolvedValueOnce({ rows: [] });
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('session_id = $'),
expect.arrayContaining(['session-123'])
);
});
it('should apply custom pagination', async () => {
mockReq.query = { limit: '50', offset: '100' };
mockPool.query.mockResolvedValueOnce({ rows: [] });
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockPool.query).toHaveBeenCalledWith(
expect.any(String),
expect.arrayContaining([50, 100])
);
});
it('should cap limit at 1000', async () => {
mockReq.query = { limit: '5000' };
mockPool.query.mockResolvedValueOnce({ rows: [] });
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockPool.query).toHaveBeenCalledWith(
expect.any(String),
expect.arrayContaining([1000])
);
});
it('should return 503 if database is not available', async () => {
require('../src/db').getPool.mockReturnValueOnce(null);
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(503);
});
it('should return 500 on database error', async () => {
mockPool.query.mockRejectedValueOnce(new Error('DB error'));
await feedbackController.listFeedbackEvents(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
error: { message: 'Failed to list feedback events' }
});
});
});
describe('getPOIStats', () => {
it('should return 400 if poi_id is missing', async () => {
mockReq.params = {};
await feedbackController.getPOIStats(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({
error: { message: 'poi_id is required' }
});
});
it('should return aggregated stats for a POI', async () => {
mockReq.params = { poi_id: 'poi-123' };
const mockEvents = [
{ event_type: 'plan_saved', reward: '1.0', modifications: '[]' },
{ event_type: 'plan_saved', reward: '0.5', modifications: '[]' },
{ event_type: 'poi_removed', reward: '-0.5', modifications: '[{"poi_id": "poi-123"}]' }
];
mockPool.query.mockResolvedValueOnce({ rows: mockEvents });
await feedbackController.getPOIStats(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith({
data: expect.objectContaining({
poi_id: 'poi-123',
times_suggested: 3,
times_saved: 2,
times_removed: 1
})
});
});
it('should calculate save_rate correctly', async () => {
mockReq.params = { poi_id: 'poi-456' };
const mockEvents = [
{ event_type: 'plan_saved', reward: '1.0', modifications: '[]' },
{ event_type: 'plan_completed', reward: '1.0', modifications: '[]' }
];
mockPool.query.mockResolvedValueOnce({ rows: mockEvents });
await feedbackController.getPOIStats(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith({
data: expect.objectContaining({
times_saved: 1,
times_suggested: 2,
save_rate: '0.50'
})
});
});
it('should return zero stats for POI with no events', async () => {
mockReq.params = { poi_id: 'new-poi' };
mockPool.query.mockResolvedValueOnce({ rows: [] });
await feedbackController.getPOIStats(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith({
data: expect.objectContaining({
poi_id: 'new-poi',
times_suggested: 0,
times_saved: 0,
times_removed: 0,
avg_reward: 0,
save_rate: 0
})
});
});
it('should return 503 if database is not available', async () => {
mockReq.params = { poi_id: 'poi-123' };
require('../src/db').getPool.mockReturnValueOnce(null);
await feedbackController.getPOIStats(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(503);
});
it('should return 500 on database error', async () => {
mockReq.params = { poi_id: 'poi-123' };
mockPool.query.mockRejectedValueOnce(new Error('DB error'));
await feedbackController.getPOIStats(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(500);
expect(mockRes.json).toHaveBeenCalledWith({
error: { message: 'Failed to get POI stats' }
});
});
});
});