// 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(), findUserById: jest.fn(), update: jest.fn(), }; // Mock the service module jest.mock('../../services/couchdbService', () => mockCouchdbService); const UserBadge = require('../../models/UserBadge'); describe('UserBadge Model', () => { beforeEach(() => { // 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 user badge', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge._id).toBeDefined(); expect(userBadge.user).toBe(userBadgeData.user); expect(userBadge.badge).toBe(userBadgeData.badge); expect(userBadge.earnedAt).toBe(userBadgeData.earnedAt); }); it('should require user field', async () => { const userBadgeData = { badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; expect(() => new UserBadge(userBadgeData)).toThrow(); }); it('should require badge field', async () => { const userBadgeData = { user: 'user_123', earnedAt: '2023-11-01T10:00:00.000Z', }; expect(() => new UserBadge(userBadgeData)).toThrow(); }); it('should require earnedAt field', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', }; expect(() => new UserBadge(userBadgeData)).toThrow(); }); }); describe('Default Values', () => { it('should default earnedAt to current time if not provided', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', }; const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, earnedAt: '2023-11-01T10:00:00.000Z', // Default current time createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge.earnedAt).toBeDefined(); expect(typeof userBadge.earnedAt).toBe('string'); }); }); describe('Unique Constraint', () => { it('should enforce unique user-badge combination', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; // Simulate existing user badge const existingUserBadge = { _id: 'user_badge_existing', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.findByType.mockResolvedValue([existingUserBadge]); // This should be handled at the service level, but we test the model validation const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge.user).toBe('user_123'); expect(userBadge.badge).toBe('badge_123'); }); }); describe('Date Validation', () => { it('should accept valid date strings for earnedAt', async () => { const validDates = [ '2023-11-01T10:00:00.000Z', '2023-12-15T14:30:00.000Z', '2024-01-20T09:15:00.000Z' ]; for (const date of validDates) { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: date, }; const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge.earnedAt).toBe(date); } }); }); describe('Relationships', () => { it('should reference user ID', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge.user).toBe('user_123'); }); it('should reference badge ID', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge.badge).toBe('badge_123'); }); }); describe('Timestamps', () => { it('should automatically set createdAt and updatedAt', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; const mockCreated = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const userBadge = await UserBadge.create(userBadgeData); expect(userBadge.createdAt).toBeDefined(); expect(userBadge.updatedAt).toBeDefined(); expect(typeof userBadge.createdAt).toBe('string'); expect(typeof userBadge.updatedAt).toBe('string'); }); it('should update updatedAt on modification', async () => { const userBadgeData = { user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', }; const mockUserBadge = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', ...userBadgeData, createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.findDocumentById.mockResolvedValue(mockUserBadge); mockCouchdbService.updateDocument.mockResolvedValue({ ...mockUserBadge, earnedAt: '2023-11-02T10:00:00.000Z', _rev: '2-def', updatedAt: '2023-01-01T00:00:01.000Z' }); const userBadge = await UserBadge.findById('user_badge_123'); const originalUpdatedAt = userBadge.updatedAt; userBadge.earnedAt = '2023-11-02T10:00:00.000Z'; await userBadge.save(); expect(userBadge.updatedAt).not.toBe(originalUpdatedAt); }); }); describe('Static Methods', () => { it('should find user badge by ID', async () => { const mockUserBadge = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.findDocumentById.mockResolvedValue(mockUserBadge); const userBadge = await UserBadge.findById('user_badge_123'); expect(userBadge).toBeDefined(); expect(userBadge._id).toBe('user_badge_123'); expect(userBadge.user).toBe('user_123'); expect(userBadge.badge).toBe('badge_123'); }); it('should return null when user badge not found', async () => { mockCouchdbService.findDocumentById.mockResolvedValue(null); const userBadge = await UserBadge.findById('nonexistent'); expect(userBadge).toBeNull(); }); it('should find badges by user ID', async () => { const mockUserBadges = [ { _id: 'user_badge_1', _rev: '1-abc', type: 'user_badge', user: 'user_123', badge: 'badge_1', earnedAt: '2023-11-01T10:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }, { _id: 'user_badge_2', _rev: '1-abc', type: 'user_badge', user: 'user_123', badge: 'badge_2', earnedAt: '2023-11-02T10:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' } ]; mockCouchdbService.findByType.mockResolvedValue(mockUserBadges); const userBadges = await UserBadge.findByUser('user_123'); expect(userBadges).toHaveLength(2); expect(userBadges[0].user).toBe('user_123'); expect(userBadges[1].user).toBe('user_123'); }); it('should find users by badge ID', async () => { const mockUserBadges = [ { _id: 'user_badge_1', _rev: '1-abc', type: 'user_badge', user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }, { _id: 'user_badge_2', _rev: '1-abc', type: 'user_badge', user: 'user_456', badge: 'badge_123', earnedAt: '2023-11-02T10:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' } ]; mockCouchdbService.findByType.mockResolvedValue(mockUserBadges); const userBadges = await UserBadge.findByBadge('badge_123'); expect(userBadges).toHaveLength(2); expect(userBadges[0].badge).toBe('badge_123'); expect(userBadges[1].badge).toBe('badge_123'); }); }); describe('Helper Methods', () => { it('should check if user has specific badge', async () => { const mockUserBadge = { _id: 'user_badge_123', _rev: '1-abc', type: 'user_badge', user: 'user_123', badge: 'badge_123', earnedAt: '2023-11-01T10:00:00.000Z', createdAt: '2023-01-01T00:00:00.000Z', updatedAt: '2023-01-01T00:00:00.000Z' }; mockCouchdbService.findByType.mockResolvedValue([mockUserBadge]); const hasBadge = await UserBadge.userHasBadge('user_123', 'badge_123'); expect(hasBadge).toBe(true); }); it('should return false if user does not have specific badge', async () => { mockCouchdbService.findByType.mockResolvedValue([]); const hasBadge = await UserBadge.userHasBadge('user_123', 'badge_456'); expect(hasBadge).toBe(false); }); }); });