const request = require('supertest'); const express = require('express'); // Mock CouchDB service before importing routes jest.mock('../../services/couchdbService', () => ({ initialize: jest.fn().mockResolvedValue(true), create: jest.fn(), getById: jest.fn(), find: jest.fn(), createDocument: jest.fn().mockImplementation((doc) => Promise.resolve({ _id: `reward_${Date.now()}`, _rev: '1-test', type: 'reward', ...doc })), updateDocument: jest.fn().mockImplementation((id, doc) => Promise.resolve({ _id: id, _rev: '2-test', ...doc })), deleteDocument: jest.fn(), findByType: jest.fn().mockResolvedValue([]), findUserById: jest.fn(), findUserByEmail: jest.fn(), update: jest.fn(), getDocument: jest.fn(), })); const rewardsRoutes = require('../../routes/rewards'); const Reward = require('../../models/Reward'); const User = require('../../models/User'); const { createTestUser, createTestReward } = require('../utils/testHelpers'); const couchdbService = require('../../services/couchdbService'); // Create Express app for testing const app = express(); app.use(express.json()); app.use('/api/rewards', rewardsRoutes); describe('Rewards Routes', () => { beforeEach(() => { jest.clearAllMocks(); // Mock Reward model methods Reward.getAllPaginated = jest.fn().mockResolvedValue({ rewards: [], pagination: { totalCount: 0 } }); Reward.findById = jest.fn().mockResolvedValue(null); Reward.create = jest.fn().mockImplementation((data) => Promise.resolve({ _id: `reward_${Date.now()}`, _rev: '1-test', type: 'reward', ...data })); Reward.redeemReward = jest.fn().mockResolvedValue({ pointsDeducted: 50, newBalance: 50, redemption: { _id: 'redemption_123', userId: 'user_123', rewardId: 'reward_123' } }); // Mock User model methods User.findById = jest.fn().mockResolvedValue({ _id: 'user_123', points: 100, isPremium: false, redeemedRewards: [], stats: { streetsAdopted: 0, tasksCompleted: 0, postsCreated: 0, eventsParticipated: 0, badgesEarned: 0 } }); User.update = jest.fn().mockResolvedValue(true); // Mock couchdbService methods couchdbService.find.mockResolvedValue([]); couchdbService.getDocument.mockResolvedValue(null); }); describe('GET /api/rewards', () => { it('should get all rewards', async () => { const reward1 = await createTestReward({ name: 'Reward 1', cost: 50 }); const reward2 = await createTestReward({ name: 'Reward 2', cost: 100 }); // Mock Reward.getAllPaginated to return test rewards Reward.getAllPaginated.mockResolvedValue({ rewards: [reward1, reward2], pagination: { totalCount: 2 } }); const response = await request(app) .get('/api/rewards') .expect(200); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(2); expect(response.body.data[0]).toHaveProperty('name'); expect(response.body.data[0]).toHaveProperty('cost'); }); it('should return empty array when no rewards exist', async () => { // Mock Reward.getAllPaginated to return empty array Reward.getAllPaginated.mockResolvedValue({ rewards: [], pagination: { totalCount: 0 } }); const response = await request(app) .get('/api/rewards') .expect(200); expect(Array.isArray(response.body.data)).toBe(true); expect(response.body.data.length).toBe(0); }); }); describe('POST /api/rewards', () => { it('should create a new reward with authentication', async () => { const { token } = await createTestUser(); const rewardData = { name: 'Test Reward', description: 'Test reward description', cost: 50, }; // Mock Reward.create to return created reward const createdReward = { _id: 'reward_123', _rev: '1-test', type: 'reward', ...rewardData, isActive: true, isPremium: false, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; Reward.create.mockResolvedValue(createdReward); const response = await request(app) .post('/api/rewards') .set('x-auth-token', token) .send(rewardData) .expect(200); expect(response.body).toHaveProperty('_id'); expect(response.body.name).toBe(rewardData.name); expect(response.body.description).toBe(rewardData.description); expect(response.body.cost).toBe(rewardData.cost); }); it('should reject reward creation without authentication', async () => { const rewardData = { name: 'Unauthorized Reward', description: 'This should fail', cost: 50, }; const response = await request(app) .post('/api/rewards') .send(rewardData) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); }); describe('POST /api/rewards/redeem/:id', () => { it('should allow user to redeem reward with sufficient points', async () => { const { user, token } = await createTestUser(); const reward = await createTestReward({ cost: 50 }); // Ensure reward ID matches validator pattern by manually setting it reward.id = 'reward_test123'; reward._id = 'reward_test123'; // Mock Reward.redeemReward to return successful redemption Reward.redeemReward.mockResolvedValue({ pointsDeducted: 50, newBalance: 50, redemption: { _id: 'redemption_123', userId: user._id, rewardId: reward._id } }); const response = await request(app) .post(`/api/rewards/redeem/${reward.id}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('msg', 'Reward redeemed successfully'); expect(response.body).toHaveProperty('pointsDeducted', 50); expect(response.body).toHaveProperty('newBalance', 50); }); it('should reject redemption without sufficient points', async () => { const { user, token } = await createTestUser(); const reward = await createTestReward({ cost: 150 }); reward.id = 'reward_test456'; reward._id = 'reward_test456'; // Mock Reward.redeemReward to throw "Not enough points" error Reward.redeemReward.mockRejectedValue(new Error('Not enough points')); const response = await request(app) .post(`/api/rewards/redeem/${reward.id}`) .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('msg', 'Not enough points'); }); it('should reject premium reward redemption for non-premium users', async () => { const { user, token } = await createTestUser(); const reward = await createTestReward({ cost: 50, isPremium: true }); reward.id = 'reward_test789'; reward._id = 'reward_test789'; // Mock Reward.redeemReward to throw "Premium reward not available" error Reward.redeemReward.mockRejectedValue(new Error('Premium reward not available')); const response = await request(app) .post(`/api/rewards/redeem/${reward.id}`) .set('x-auth-token', token) .expect(403); expect(response.body).toHaveProperty('msg', 'Premium reward not available'); }); it('should allow premium users to redeem premium rewards', async () => { const { user, token } = await createTestUser({ isPremium: true }); const reward = await createTestReward({ cost: 50, isPremium: true }); reward.id = 'reward_test999'; reward._id = 'reward_test999'; // Mock Reward.redeemReward to return successful redemption Reward.redeemReward.mockResolvedValue({ pointsDeducted: 50, newBalance: 50, redemption: { _id: 'redemption_123', userId: user._id, rewardId: reward._id } }); const response = await request(app) .post(`/api/rewards/redeem/${reward.id}`) .set('x-auth-token', token) .expect(200); expect(response.body).toHaveProperty('msg', 'Reward redeemed successfully'); expect(response.body).toHaveProperty('pointsDeducted', 50); expect(response.body).toHaveProperty('newBalance', 50); }); it('should return 404 for non-existent reward', async () => { const { token } = await createTestUser(); const fakeId = '507f1f77bcf86cd799439011'; // Mock Reward.redeemReward to throw "Reward not found" error Reward.redeemReward.mockRejectedValue(new Error('Reward not found')); const response = await request(app) .post(`/api/rewards/redeem/${fakeId}`) .set('x-auth-token', token) .expect(404); expect(response.body).toHaveProperty('msg', 'Reward not found'); }); it('should reject redemption without authentication', async () => { const { user } = await createTestUser(); const reward = await createTestReward({ cost: 50 }); const response = await request(app) .post(`/api/rewards/redeem/${reward.id}`) .expect(401); expect(response.body).toHaveProperty('msg', 'No token, authorization denied'); }); it('should handle invalid reward ID format', async () => { const { token } = await createTestUser(); const response = await request(app) .post('/api/rewards/redeem/invalid-id') .set('x-auth-token', token) .expect(400); expect(response.body).toHaveProperty('success', false); expect(response.body.errors).toBeDefined(); }); }); });