252 lines
7.9 KiB
TypeScript
252 lines
7.9 KiB
TypeScript
/**
|
|
* Application Use Case Tests: OpenAdminVoteSessionUseCase
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { OpenAdminVoteSessionUseCase } from './OpenAdminVoteSessionUseCase';
|
|
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('OpenAdminVoteSessionUseCase', () => {
|
|
let useCase: OpenAdminVoteSessionUseCase;
|
|
let mockRepository: ReturnType<typeof createMockRepository>;
|
|
|
|
beforeEach(() => {
|
|
mockRepository = createMockRepository();
|
|
useCase = new OpenAdminVoteSessionUseCase(mockRepository as unknown as AdminVoteSessionRepository);
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('Input validation', () => {
|
|
it('should reject when voteSessionId is missing', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: '',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('voteSessionId is required');
|
|
});
|
|
|
|
it('should reject when leagueId is missing', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: '',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('leagueId is required');
|
|
});
|
|
|
|
it('should reject when adminId is missing', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: '',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('adminId is required');
|
|
});
|
|
|
|
it('should reject when startDate is missing', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('startDate is required');
|
|
});
|
|
|
|
it('should reject when endDate is missing', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('endDate is required');
|
|
});
|
|
|
|
it('should reject when startDate is invalid', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: 'invalid-date',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('startDate must be a valid date');
|
|
});
|
|
|
|
it('should reject when endDate is invalid', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: 'invalid-date',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('endDate must be a valid date');
|
|
});
|
|
|
|
it('should reject when startDate is after endDate', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-07',
|
|
endDate: '2026-01-01',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('startDate must be before endDate');
|
|
});
|
|
|
|
it('should reject when eligibleVoters is empty', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: [],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('At least one eligible voter is required');
|
|
});
|
|
|
|
it('should reject when eligibleVoters has duplicates', async () => {
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1', 'voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('Duplicate eligible voters are not allowed');
|
|
});
|
|
});
|
|
|
|
describe('Business rules', () => {
|
|
it('should reject when session ID already exists', async () => {
|
|
mockRepository.findById.mockResolvedValue({ id: 'session-1' } as any);
|
|
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('Vote session with this ID already exists');
|
|
});
|
|
|
|
it('should reject when there is an overlapping active session', async () => {
|
|
mockRepository.findById.mockResolvedValue(null);
|
|
mockRepository.findActiveForAdmin.mockResolvedValue([
|
|
{
|
|
startDate: new Date('2026-01-05'),
|
|
endDate: new Date('2026-01-10'),
|
|
}
|
|
] as any);
|
|
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors).toContain('Active vote session already exists for this admin in this league with overlapping dates');
|
|
});
|
|
|
|
it('should create and save a new session when valid', async () => {
|
|
mockRepository.findById.mockResolvedValue(null);
|
|
mockRepository.findActiveForAdmin.mockResolvedValue([]);
|
|
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1', 'voter-2'],
|
|
});
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(mockRepository.save).toHaveBeenCalled();
|
|
const savedSession = mockRepository.save.mock.calls[0][0];
|
|
expect(savedSession).toBeInstanceOf(AdminVoteSession);
|
|
expect(savedSession.id).toBe('session-1');
|
|
expect(savedSession.leagueId).toBe('league-1');
|
|
expect(savedSession.adminId).toBe('admin-1');
|
|
});
|
|
});
|
|
|
|
describe('Error handling', () => {
|
|
it('should handle repository errors gracefully', async () => {
|
|
mockRepository.findById.mockRejectedValue(new Error('Database error'));
|
|
|
|
const result = await useCase.execute({
|
|
voteSessionId: 'session-1',
|
|
leagueId: 'league-1',
|
|
adminId: 'admin-1',
|
|
startDate: '2026-01-01',
|
|
endDate: '2026-01-07',
|
|
eligibleVoters: ['voter-1'],
|
|
});
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.errors?.[0]).toContain('Failed to open vote session: Database error');
|
|
});
|
|
});
|
|
});
|