/** * Application Use Case Tests: CastAdminVoteUseCase * * Tests for casting votes in admin vote sessions */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { CastAdminVoteUseCase } from './CastAdminVoteUseCase'; import { AdminVoteSessionRepository } from '../../domain/repositories/AdminVoteSessionRepository'; import { AdminVoteSession } from '../../domain/entities/AdminVoteSession'; // Mock repository const createMockRepository = () => ({ save: vi.fn(), findById: vi.fn(), findActiveForAdmin: vi.fn(), findByAdminAndLeague: vi.fn(), findByLeague: vi.fn(), findClosedUnprocessed: vi.fn(), }); describe('CastAdminVoteUseCase', () => { let useCase: CastAdminVoteUseCase; let mockRepository: ReturnType; beforeEach(() => { mockRepository = createMockRepository(); useCase = new CastAdminVoteUseCase(mockRepository); }); describe('Input validation', () => { it('should reject when voteSessionId is missing', async () => { const result = await useCase.execute({ voteSessionId: '', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('voteSessionId is required'); }); it('should reject when voterId is missing', async () => { const result = await useCase.execute({ voteSessionId: 'session-123', voterId: '', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('voterId is required'); }); it('should reject when positive is not a boolean', async () => { const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: 'true' as any, }); expect(result.success).toBe(false); expect(result.errors).toContain('positive must be a boolean value'); }); it('should reject when votedAt is not a valid date', async () => { const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, votedAt: 'invalid-date', }); expect(result.success).toBe(false); expect(result.errors).toContain('votedAt must be a valid date if provided'); }); it('should accept valid input with all fields', async () => { mockRepository.findById.mockResolvedValue({ id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, votedAt: '2024-01-01T00:00:00Z', }); expect(result.success).toBe(true); expect(result.errors).toBeUndefined(); }); it('should accept valid input without optional votedAt', async () => { mockRepository.findById.mockResolvedValue({ id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(true); expect(result.errors).toBeUndefined(); }); }); describe('Session lookup', () => { it('should reject when vote session is not found', async () => { mockRepository.findById.mockResolvedValue(null); const result = await useCase.execute({ voteSessionId: 'non-existent-session', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('Vote session not found'); }); it('should find session by ID when provided', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(mockRepository.findById).toHaveBeenCalledWith('session-123'); }); }); describe('Voting window validation', () => { it('should reject when voting window is not open', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(false), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('Vote session is not open for voting'); expect(mockSession.isVotingWindowOpen).toHaveBeenCalled(); }); it('should accept when voting window is open', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(true); expect(mockSession.isVotingWindowOpen).toHaveBeenCalled(); }); it('should use current time when votedAt is not provided', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(mockSession.isVotingWindowOpen).toHaveBeenCalledWith(expect.any(Date)); }); it('should use provided votedAt when available', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); const votedAt = new Date('2024-01-01T12:00:00Z'); await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, votedAt: votedAt.toISOString(), }); expect(mockSession.isVotingWindowOpen).toHaveBeenCalledWith(votedAt); }); }); describe('Vote casting', () => { it('should cast positive vote when session is open', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(mockSession.castVote).toHaveBeenCalledWith('voter-123', true, expect.any(Date)); }); it('should cast negative vote when session is open', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: false, }); expect(mockSession.castVote).toHaveBeenCalledWith('voter-123', false, expect.any(Date)); }); it('should save updated session after casting vote', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(mockRepository.save).toHaveBeenCalledWith(mockSession); }); it('should return success when vote is cast', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(true); expect(result.voteSessionId).toBe('session-123'); expect(result.voterId).toBe('voter-123'); expect(result.errors).toBeUndefined(); }); }); describe('Error handling', () => { it('should handle repository errors gracefully', async () => { mockRepository.findById.mockRejectedValue(new Error('Database error')); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('Failed to cast vote: Database error'); }); it('should handle unexpected errors gracefully', async () => { mockRepository.findById.mockRejectedValue('Unknown error'); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('Failed to cast vote: Unknown error'); }); it('should handle save errors gracefully', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); mockRepository.save.mockRejectedValue(new Error('Save failed')); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.success).toBe(false); expect(result.errors).toContain('Failed to cast vote: Save failed'); }); }); describe('Return values', () => { it('should return voteSessionId in success response', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.voteSessionId).toBe('session-123'); }); it('should return voterId in success response', async () => { const mockSession = { id: 'session-123', isVotingWindowOpen: vi.fn().mockReturnValue(true), castVote: vi.fn(), }; mockRepository.findById.mockResolvedValue(mockSession); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.voterId).toBe('voter-123'); }); it('should return voteSessionId in error response', async () => { mockRepository.findById.mockResolvedValue(null); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.voteSessionId).toBe('session-123'); }); it('should return voterId in error response', async () => { mockRepository.findById.mockResolvedValue(null); const result = await useCase.execute({ voteSessionId: 'session-123', voterId: 'voter-123', positive: true, }); expect(result.voterId).toBe('voter-123'); }); }); });