Files
gridpilot.gg/core/identity/application/use-cases/CloseAdminVoteSessionUseCase.test.ts
Marc Mintel afef777961
Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
code quality
2026-01-26 02:27:37 +01:00

1218 lines
38 KiB
TypeScript

/**
* Application Use Case Tests: CloseAdminVoteSessionUseCase
*
* Tests for closing admin vote sessions and generating rating events
*/
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
import { CloseAdminVoteSessionUseCase } from './CloseAdminVoteSessionUseCase';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { RatingSnapshotCalculator } from '../../domain/services/RatingSnapshotCalculator';
import { AdminVoteSessionRepository } from '../../domain/repositories/AdminVoteSessionRepository';
import { RatingEventRepository } from '../../domain/repositories/RatingEventRepository';
import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { AdminVoteSession, AdminVoteOutcome } from '../../domain/entities/AdminVoteSession';
// Mock repositories
const createMockRepositories = () => ({
adminVoteSessionRepository: {
save: vi.fn(),
findById: vi.fn(),
findActiveForAdmin: vi.fn(),
findByAdminAndLeague: vi.fn(),
findByLeague: vi.fn(),
findClosedUnprocessed: vi.fn(),
},
ratingEventRepository: {
save: vi.fn(),
findByUserId: vi.fn(),
findByIds: vi.fn(),
getAllByUserId: vi.fn(),
findEventsPaginated: vi.fn(),
},
userRatingRepository: {
save: vi.fn(),
},
});
// Mock services
vi.mock('../../domain/services/RatingEventFactory', () => ({
RatingEventFactory: {
createFromVote: vi.fn(),
},
}));
vi.mock('../../domain/services/RatingSnapshotCalculator', () => ({
RatingSnapshotCalculator: {
calculate: vi.fn(),
},
}));
describe('CloseAdminVoteSessionUseCase', () => {
let useCase: CloseAdminVoteSessionUseCase;
let mockRepositories: ReturnType<typeof createMockRepositories>;
beforeEach(() => {
mockRepositories = createMockRepositories();
useCase = new CloseAdminVoteSessionUseCase(
mockRepositories.adminVoteSessionRepository as unknown as AdminVoteSessionRepository,
mockRepositories.ratingEventRepository as unknown as RatingEventRepository,
mockRepositories.userRatingRepository as unknown as UserRatingRepository
);
vi.clearAllMocks();
// Default mock for RatingEventFactory.createFromVote to return an empty array
// to avoid "events is not iterable" error in tests that don't explicitly mock it
(RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([]);
});
describe('Input validation', () => {
it('should reject when voteSessionId is missing', async () => {
const result = await useCase.execute({
voteSessionId: '',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('voteSessionId is required');
});
it('should reject when adminId is missing', async () => {
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: '',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('adminId is required');
});
it('should accept valid input', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
console.log('Result:', JSON.stringify(result, null, 2));
console.log('Mock session closed:', mockSession.closed);
console.log('Mock session _closed:', mockSession._closed);
console.log('Mock session close called:', mockSession.close.mock.calls.length);
expect(result.success).toBe(true);
expect(result.errors).toBeUndefined();
});
});
describe('Session lookup', () => {
it('should reject when vote session is not found', async () => {
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(null);
const result = await useCase.execute({
voteSessionId: 'non-existent-session',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Vote session not found');
});
it('should find session by ID when provided', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(mockRepositories.adminVoteSessionRepository.findById).toHaveBeenCalledWith('session-123');
});
});
describe('Admin ownership validation', () => {
it('should reject when admin does not own the session', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'different-admin',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Admin does not own this vote session');
});
it('should accept when admin owns the session', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(true);
});
});
describe('Session closure validation', () => {
it('should reject when session is already closed', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: true,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Vote session is already closed');
});
it('should accept when session is not closed', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(true);
});
});
describe('Voting window validation', () => {
it('should reject when trying to close outside voting window', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
// Mock Date to be outside the window
const originalDate = Date;
global.Date = class extends originalDate {
constructor() {
super('2026-02-02');
}
} as unknown as typeof Date;
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Cannot close session outside the voting window');
// Restore Date
global.Date = originalDate;
});
it('should accept when trying to close within voting window', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
// Mock Date to be within the window
const originalDate = Date;
global.Date = class extends originalDate {
constructor() {
super('2026-01-15T12:00:00');
}
} as unknown as typeof Date;
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(true);
// Restore Date
global.Date = originalDate;
});
});
describe('Session closure', () => {
it('should call close method on session', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(mockSession.close).toHaveBeenCalled();
});
it('should save closed session', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(mockRepositories.adminVoteSessionRepository.save).toHaveBeenCalledWith(mockSession);
});
it('should return outcome in success response', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(true);
expect(result.outcome).toBeDefined();
expect(result.outcome?.percentPositive).toBe(75);
expect(result.outcome?.count).toEqual({ positive: 3, negative: 1, total: 4 });
expect(result.outcome?.eligibleVoterCount).toBe(4);
expect(result.outcome?.participationRate).toBe(100);
expect(result.outcome?.outcome).toBe('positive');
});
});
describe('Rating event creation', () => {
it('should create rating events when outcome is positive', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const mockEvent = { id: 'event-123' };
(RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent]);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(RatingEventFactory.createFromVote).toHaveBeenCalledWith({
userId: 'admin-123',
voteSessionId: 'session-123',
outcome: 'positive',
voteCount: 4,
eligibleVoterCount: 4,
percentPositive: 75,
});
});
it('should create rating events when outcome is negative', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 25,
count: { positive: 1, negative: 3, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'negative',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const mockEvent = { id: 'event-123' };
(RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent]);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(RatingEventFactory.createFromVote).toHaveBeenCalledWith({
userId: 'admin-123',
voteSessionId: 'session-123',
outcome: 'negative',
voteCount: 4,
eligibleVoterCount: 4,
percentPositive: 25,
});
});
it('should not create rating events when outcome is tie', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 50,
count: { positive: 2, negative: 2, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'tie',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(RatingEventFactory.createFromVote).not.toHaveBeenCalled();
expect(mockRepositories.ratingEventRepository.save).not.toHaveBeenCalled();
});
it('should save created rating events', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const mockEvent1 = { id: 'event-123' };
const mockEvent2 = { id: 'event-124' };
(RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent1, mockEvent2]);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(mockRepositories.ratingEventRepository.save).toHaveBeenCalledTimes(2);
expect(mockRepositories.ratingEventRepository.save).toHaveBeenCalledWith(mockEvent1);
expect(mockRepositories.ratingEventRepository.save).toHaveBeenCalledWith(mockEvent2);
});
it('should return eventsCreated count', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const mockEvent1 = { id: 'event-123' };
const mockEvent2 = { id: 'event-124' };
(RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent1, mockEvent2]);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.eventsCreated).toBe(2);
});
});
describe('Snapshot recalculation', () => {
it('should recalculate snapshot when events are created', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const mockEvent = { id: 'event-123' };
(RatingEventFactory.createFromVote as unknown as Mock).mockReturnValue([mockEvent]);
const mockAllEvents = [{ id: 'event-1' }, { id: 'event-2' }];
mockRepositories.ratingEventRepository.getAllByUserId.mockResolvedValue(mockAllEvents);
const mockSnapshot = { userId: 'admin-123', overallReputation: 75 };
(RatingSnapshotCalculator.calculate as unknown as Mock).mockReturnValue(mockSnapshot);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(mockRepositories.ratingEventRepository.getAllByUserId).toHaveBeenCalledWith('admin-123');
expect(RatingSnapshotCalculator.calculate).toHaveBeenCalledWith('admin-123', mockAllEvents);
expect(mockRepositories.userRatingRepository.save).toHaveBeenCalledWith(mockSnapshot);
});
it('should not recalculate snapshot when no events are created (tie)', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 50,
count: { positive: 2, negative: 2, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'tie',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(mockRepositories.ratingEventRepository.getAllByUserId).not.toHaveBeenCalled();
expect(RatingSnapshotCalculator.calculate).not.toHaveBeenCalled();
expect(mockRepositories.userRatingRepository.save).not.toHaveBeenCalled();
});
});
describe('Error handling', () => {
it('should handle repository errors gracefully', async () => {
mockRepositories.adminVoteSessionRepository.findById.mockRejectedValue(new Error('Database error'));
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Failed to close vote session: Database error');
});
it('should handle unexpected errors gracefully', async () => {
mockRepositories.adminVoteSessionRepository.findById.mockRejectedValue('Unknown error');
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Failed to close vote session: Unknown error');
});
it('should handle save errors gracefully', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
mockRepositories.adminVoteSessionRepository.save.mockRejectedValue(new Error('Save failed'));
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.success).toBe(false);
expect(result.errors).toContain('Failed to close vote session: Save failed');
});
});
describe('Return values', () => {
it('should return voteSessionId in success response', async () => {
const futureDate = new Date('2026-02-01');
const mockSession: {
id: string;
adminId: string;
startDate: Date;
endDate: Date;
_closed: boolean;
_outcome?: AdminVoteOutcome;
close: Mock;
closed: boolean;
} = {
id: 'session-123',
adminId: 'admin-123',
startDate: new Date('2026-01-01'),
endDate: futureDate,
_closed: false,
close: vi.fn().mockImplementation(function() {
if (this._closed) {
throw new Error('Session is already closed');
}
const now = new Date();
if (now < this.startDate || now > this.endDate) {
throw new Error('Cannot close session outside the voting window');
}
this._closed = true;
this._outcome = {
percentPositive: 75,
count: { positive: 3, negative: 1, total: 4 },
eligibleVoterCount: 4,
participationRate: 100,
outcome: 'positive',
};
return this._outcome;
}),
get closed() {
return this._closed;
},
};
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(mockSession);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.voteSessionId).toBe('session-123');
});
it('should return voteSessionId in error response', async () => {
mockRepositories.adminVoteSessionRepository.findById.mockResolvedValue(null);
const result = await useCase.execute({
voteSessionId: 'session-123',
adminId: 'admin-123',
});
expect(result.voteSessionId).toBe('session-123');
});
});
});