254 lines
8.6 KiB
TypeScript
254 lines
8.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { TeamJoinService } from './TeamJoinService';
|
|
import { TeamsApiClient } from '../../api/teams/TeamsApiClient';
|
|
import { TeamJoinRequestPresenter } from '../../presenters/TeamJoinRequestPresenter';
|
|
import type { TeamJoinRequestsDto, TeamJoinRequestItemDto } from '../../dtos';
|
|
import type { TeamJoinRequestViewModel } from '../../view-models';
|
|
|
|
describe('TeamJoinService', () => {
|
|
let mockApiClient: TeamsApiClient;
|
|
let mockPresenter: TeamJoinRequestPresenter;
|
|
let service: TeamJoinService;
|
|
|
|
beforeEach(() => {
|
|
mockApiClient = {
|
|
getJoinRequests: vi.fn(),
|
|
} as unknown as TeamsApiClient;
|
|
|
|
mockPresenter = {
|
|
present: vi.fn(),
|
|
} as unknown as TeamJoinRequestPresenter;
|
|
|
|
service = new TeamJoinService(mockApiClient, mockPresenter);
|
|
});
|
|
|
|
describe('getJoinRequests', () => {
|
|
it('should fetch join requests from API and transform via presenter', async () => {
|
|
// Arrange
|
|
const mockRequestDto: TeamJoinRequestItemDto = {
|
|
id: 'request-1',
|
|
teamId: 'team-1',
|
|
driverId: 'driver-1',
|
|
requestedAt: '2025-12-17T20:00:00Z',
|
|
message: 'Please let me join',
|
|
};
|
|
|
|
const mockDto: TeamJoinRequestsDto = {
|
|
requests: [mockRequestDto],
|
|
};
|
|
|
|
const mockViewModel: TeamJoinRequestViewModel = {
|
|
id: 'request-1',
|
|
teamId: 'team-1',
|
|
driverId: 'driver-1',
|
|
requestedAt: '2025-12-17T20:00:00Z',
|
|
message: 'Please let me join',
|
|
canApprove: true,
|
|
formattedRequestedAt: '12/17/2025, 9:00:00 PM',
|
|
status: 'Pending',
|
|
statusColor: 'yellow',
|
|
approveButtonText: 'Approve',
|
|
rejectButtonText: 'Reject',
|
|
} as unknown as TeamJoinRequestViewModel;
|
|
|
|
vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
|
|
vi.mocked(mockPresenter.present).mockReturnValue(mockViewModel);
|
|
|
|
// Act
|
|
const result = await service.getJoinRequests('team-1', 'driver-owner', true);
|
|
|
|
// Assert
|
|
expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
|
|
expect(mockApiClient.getJoinRequests).toHaveBeenCalledTimes(1);
|
|
expect(mockPresenter.present).toHaveBeenCalledWith(mockRequestDto, 'driver-owner', true);
|
|
expect(mockPresenter.present).toHaveBeenCalledTimes(1);
|
|
expect(result).toEqual([mockViewModel]);
|
|
});
|
|
|
|
it('should handle multiple join requests', async () => {
|
|
// Arrange
|
|
const mockRequestDto1: TeamJoinRequestItemDto = {
|
|
id: 'request-1',
|
|
teamId: 'team-1',
|
|
driverId: 'driver-1',
|
|
requestedAt: '2025-12-17T20:00:00Z',
|
|
};
|
|
|
|
const mockRequestDto2: TeamJoinRequestItemDto = {
|
|
id: 'request-2',
|
|
teamId: 'team-1',
|
|
driverId: 'driver-2',
|
|
requestedAt: '2025-12-17T21:00:00Z',
|
|
message: 'I want to join',
|
|
};
|
|
|
|
const mockDto: TeamJoinRequestsDto = {
|
|
requests: [mockRequestDto1, mockRequestDto2],
|
|
};
|
|
|
|
const mockViewModel1 = { id: 'request-1' } as TeamJoinRequestViewModel;
|
|
const mockViewModel2 = { id: 'request-2' } as TeamJoinRequestViewModel;
|
|
|
|
vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
|
|
vi.mocked(mockPresenter.present)
|
|
.mockReturnValueOnce(mockViewModel1)
|
|
.mockReturnValueOnce(mockViewModel2);
|
|
|
|
// Act
|
|
const result = await service.getJoinRequests('team-1', 'driver-owner', true);
|
|
|
|
// Assert
|
|
expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
|
|
expect(mockPresenter.present).toHaveBeenCalledTimes(2);
|
|
expect(mockPresenter.present).toHaveBeenNthCalledWith(1, mockRequestDto1, 'driver-owner', true);
|
|
expect(mockPresenter.present).toHaveBeenNthCalledWith(2, mockRequestDto2, 'driver-owner', true);
|
|
expect(result).toEqual([mockViewModel1, mockViewModel2]);
|
|
});
|
|
|
|
it('should handle empty join requests list', async () => {
|
|
// Arrange
|
|
const mockDto: TeamJoinRequestsDto = {
|
|
requests: [],
|
|
};
|
|
|
|
vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
|
|
|
|
// Act
|
|
const result = await service.getJoinRequests('team-1', 'driver-owner', true);
|
|
|
|
// Assert
|
|
expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
|
|
expect(mockPresenter.present).not.toHaveBeenCalled();
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('should propagate API client errors', async () => {
|
|
// Arrange
|
|
const error = new Error('API Error: Team not found');
|
|
vi.mocked(mockApiClient.getJoinRequests).mockRejectedValue(error);
|
|
|
|
// Act & Assert
|
|
await expect(
|
|
service.getJoinRequests('invalid-team', 'driver-1', false)
|
|
).rejects.toThrow('API Error: Team not found');
|
|
|
|
expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('invalid-team');
|
|
expect(mockPresenter.present).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should propagate presenter errors', async () => {
|
|
// Arrange
|
|
const mockRequestDto: TeamJoinRequestItemDto = {
|
|
id: 'request-1',
|
|
teamId: 'team-1',
|
|
driverId: 'driver-1',
|
|
requestedAt: '2025-12-17T20:00:00Z',
|
|
};
|
|
|
|
const mockDto: TeamJoinRequestsDto = {
|
|
requests: [mockRequestDto],
|
|
};
|
|
|
|
const error = new Error('Presenter Error: Invalid DTO structure');
|
|
vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
|
|
vi.mocked(mockPresenter.present).mockImplementation(() => {
|
|
throw error;
|
|
});
|
|
|
|
// Act & Assert
|
|
await expect(
|
|
service.getJoinRequests('team-1', 'driver-1', false)
|
|
).rejects.toThrow('Presenter Error: Invalid DTO structure');
|
|
|
|
expect(mockApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
|
|
expect(mockPresenter.present).toHaveBeenCalledWith(mockRequestDto, 'driver-1', false);
|
|
});
|
|
|
|
it('should pass correct isOwner flag to presenter', async () => {
|
|
// Arrange
|
|
const mockRequestDto: TeamJoinRequestItemDto = {
|
|
id: 'request-1',
|
|
teamId: 'team-1',
|
|
driverId: 'driver-1',
|
|
requestedAt: '2025-12-17T20:00:00Z',
|
|
};
|
|
|
|
const mockDto: TeamJoinRequestsDto = {
|
|
requests: [mockRequestDto],
|
|
};
|
|
|
|
const mockViewModel = { id: 'request-1' } as TeamJoinRequestViewModel;
|
|
|
|
vi.mocked(mockApiClient.getJoinRequests).mockResolvedValue(mockDto);
|
|
vi.mocked(mockPresenter.present).mockReturnValue(mockViewModel);
|
|
|
|
// Act - non-owner
|
|
await service.getJoinRequests('team-1', 'driver-member', false);
|
|
|
|
// Assert
|
|
expect(mockPresenter.present).toHaveBeenCalledWith(mockRequestDto, 'driver-member', false);
|
|
});
|
|
});
|
|
|
|
describe('approveJoinRequest', () => {
|
|
it('should throw not implemented error', async () => {
|
|
// Act & Assert
|
|
await expect(
|
|
service.approveJoinRequest('team-1', 'request-1')
|
|
).rejects.toThrow('Not implemented: API endpoint for approving join requests');
|
|
});
|
|
|
|
it('should propagate errors when API is implemented', async () => {
|
|
// This test ensures error handling is in place for future implementation
|
|
// Act & Assert
|
|
await expect(
|
|
service.approveJoinRequest('team-1', 'request-1')
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('rejectJoinRequest', () => {
|
|
it('should throw not implemented error', async () => {
|
|
// Act & Assert
|
|
await expect(
|
|
service.rejectJoinRequest('team-1', 'request-1')
|
|
).rejects.toThrow('Not implemented: API endpoint for rejecting join requests');
|
|
});
|
|
|
|
it('should propagate errors when API is implemented', async () => {
|
|
// This test ensures error handling is in place for future implementation
|
|
// Act & Assert
|
|
await expect(
|
|
service.rejectJoinRequest('team-1', 'request-1')
|
|
).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('Constructor Dependency Injection', () => {
|
|
it('should require apiClient and teamJoinRequestPresenter', () => {
|
|
// This test verifies the constructor signature
|
|
expect(() => {
|
|
new TeamJoinService(mockApiClient, mockPresenter);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should use injected dependencies', async () => {
|
|
// Arrange
|
|
const customApiClient = {
|
|
getJoinRequests: vi.fn().mockResolvedValue({ requests: [] }),
|
|
} as unknown as TeamsApiClient;
|
|
|
|
const customPresenter = {
|
|
present: vi.fn().mockReturnValue({} as TeamJoinRequestViewModel),
|
|
} as unknown as TeamJoinRequestPresenter;
|
|
|
|
const customService = new TeamJoinService(customApiClient, customPresenter);
|
|
|
|
// Act
|
|
await customService.getJoinRequests('team-1', 'driver-1', true);
|
|
|
|
// Assert
|
|
expect(customApiClient.getJoinRequests).toHaveBeenCalledWith('team-1');
|
|
});
|
|
});
|
|
}); |