// Mock CouchDB service for testing const mockCouchdbService = { createDocument: jest.fn(), findDocumentById: jest.fn(), updateDocument: jest.fn(), findByType: jest.fn(), initialize: jest.fn(), getDocument: jest.fn(), }; // Mock the service module jest.mock('../../services/couchdbService', () => mockCouchdbService); const Badge = require('../../models/Badge'); describe('Badge Model', () => { beforeEach(() => { jest.clearAllMocks(); // Reset all mocks to ensure clean state mockCouchdbService.createDocument.mockReset(); mockCouchdbService.findDocumentById.mockReset(); mockCouchdbService.updateDocument.mockReset(); mockCouchdbService.findByType.mockReset(); }); describe('Schema Validation', () => { it('should create a valid badge', async () => { const badgeData = { name: 'Street Cleaner', description: 'Awarded for completing 10 street cleaning tasks', icon: 'broom', category: 'maintenance', requirement: { type: 'task_count', value: 10, taskType: 'cleaning' } }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 50, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge._id).toBeDefined(); expect(badge.name).toBe(badgeData.name); expect(badge.description).toBe(badgeData.description); expect(badge.icon).toBe(badgeData.icon); expect(badge.category).toBe(badgeData.category); expect(badge.requirement.type).toBe(badgeData.requirement.type); expect(badge.isActive).toBe(true); expect(badge.pointsAwarded).toBe(50); }); it('should require name field', async () => { const badgeData = { description: 'Badge without name', icon: 'star', category: 'achievement', }; expect(() => new Badge(badgeData)).toThrow(); }); it('should require description field', async () => { const badgeData = { name: 'Badge without description', icon: 'star', category: 'achievement', }; expect(() => new Badge(badgeData)).toThrow(); }); it('should require icon field', async () => { const badgeData = { name: 'Badge without icon', description: 'This badge has no icon', category: 'achievement', }; expect(() => new Badge(badgeData)).toThrow(); }); it('should require category field', async () => { const badgeData = { name: 'Badge without category', description: 'This badge has no category', icon: 'star', }; expect(() => new Badge(badgeData)).toThrow(); }); it('should require requirement field', async () => { const badgeData = { name: 'Badge without requirement', description: 'This badge has no requirement', icon: 'star', category: 'achievement', }; expect(() => new Badge(badgeData)).toThrow(); }); }); describe('Categories', () => { const validCategories = ['achievement', 'maintenance', 'social', 'milestone', 'special']; validCategories.forEach(category => { it(`should accept "${category}" as valid category`, async () => { const badgeData = { name: `${category} Badge`, description: `Testing ${category} category`, icon: 'star', category, requirement: { type: 'task_count', value: 5 } }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.category).toBe(category); }); }); it('should reject invalid category', async () => { const badgeData = { name: 'Invalid Category Badge', description: 'This badge has invalid category', icon: 'star', category: 'invalid_category', requirement: { type: 'task_count', value: 5 } }; expect(() => new Badge(badgeData)).toThrow(); }); }); describe('Requirement Types', () => { const validRequirementTypes = [ { type: 'task_count', value: 10 }, { type: 'street_count', value: 5 }, { type: 'points_earned', value: 1000 }, { type: 'event_participation', value: 3 }, { type: 'streak_days', value: 7 } ]; validRequirementTypes.forEach(requirement => { it(`should accept "${requirement.type}" as valid requirement type`, async () => { const badgeData = { name: `${requirement.type} Badge`, description: `Testing ${requirement.type} requirement`, icon: 'star', category: 'achievement', requirement }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.requirement.type).toBe(requirement.type); expect(badge.requirement.value).toBe(requirement.value); }); }); }); describe('Default Values', () => { it('should default isActive to true', async () => { const badgeData = { name: 'Default Active Badge', description: 'Testing default active status', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 } }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.isActive).toBe(true); }); it('should default pointsAwarded to 25', async () => { const badgeData = { name: 'Default Points Badge', description: 'Testing default points', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 } }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.pointsAwarded).toBe(25); }); }); describe('Custom Values', () => { it('should allow custom isActive value', async () => { const badgeData = { name: 'Inactive Badge', description: 'This badge is inactive', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 }, isActive: false }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.isActive).toBe(false); }); it('should allow custom pointsAwarded value', async () => { const badgeData = { name: 'Custom Points Badge', description: 'This badge gives custom points', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 }, pointsAwarded: 100 }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.pointsAwarded).toBe(100); }); }); describe('Complex Requirements', () => { it('should allow requirements with additional properties', async () => { const badgeData = { name: 'Complex Requirement Badge', description: 'Badge with complex requirement', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 10, taskType: 'cleaning', timeFrame: '30_days', streetStatus: 'adopted' } }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.requirement.taskType).toBe('cleaning'); expect(badge.requirement.timeFrame).toBe('30_days'); expect(badge.requirement.streetStatus).toBe('adopted'); }); }); describe('Timestamps', () => { it('should automatically set createdAt and updatedAt', async () => { const badgeData = { name: 'Timestamp Badge', description: 'Testing timestamps', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 } }; const mockCreated = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const badge = await Badge.create(badgeData); expect(badge.createdAt).toBeDefined(); expect(badge.updatedAt).toBeDefined(); expect(typeof badge.createdAt).toBe('string'); expect(typeof badge.updatedAt).toBe('string'); }); it('should update updatedAt on modification', async () => { const badgeData = { name: 'Update Test Badge', description: 'Testing update timestamp', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 } }; const mockBadge = { _id: 'badge_123', _rev: '1-abc', type: 'badge', ...badgeData, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; couchdbService.findDocumentById.mockResolvedValue(mockBadge); couchdbService.updateDocument.mockResolvedValue({ ...mockBadge, isActive: false, _rev: '2-def', updatedAt: '2023-01-01T00:00:01.000Z' }); const badge = await Badge.findById('badge_123'); const originalUpdatedAt = badge.updatedAt; badge.isActive = false; await badge.save(); expect(badge.updatedAt).not.toBe(originalUpdatedAt); }); }); describe('Static Methods', () => { it('should find badge by ID', async () => { const mockBadge = { _id: 'badge_123', _rev: '1-abc', type: 'badge', name: 'Test Badge', description: 'Test description', icon: 'star', category: 'achievement', requirement: { type: 'task_count', value: 5 }, isActive: true, pointsAwarded: 25, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; couchdbService.findDocumentById.mockResolvedValue(mockBadge); const badge = await Badge.findById('badge_123'); expect(badge).toBeDefined(); expect(badge._id).toBe('badge_123'); expect(badge.name).toBe('Test Badge'); }); it('should return null when badge not found', async () => { couchdbService.findDocumentById.mockResolvedValue(null); const badge = await Badge.findById('nonexistent'); expect(badge).toBeNull(); }); }); });