import { describe, it, expect, vi, beforeEach } from 'vitest'; import { TeamService } from './TeamService'; import type { TeamsApiClient } from '../../api/teams/TeamsApiClient'; import type { TeamDetailsPresenter } from '../../presenters/TeamDetailsPresenter'; import type { TeamListPresenter } from '../../presenters/TeamListPresenter'; import type { TeamMembersPresenter } from '../../presenters/TeamMembersPresenter'; import type { AllTeamsDto, TeamDetailsDto, TeamMembersDto, CreateTeamInputDto, CreateTeamOutputDto, UpdateTeamInputDto, UpdateTeamOutputDto, DriverTeamDto, } from '../../dtos'; import type { TeamSummaryViewModel, TeamDetailsViewModel, TeamMemberViewModel } from '../../view-models'; describe('TeamService', () => { let service: TeamService; let mockApiClient: TeamsApiClient; let mockTeamListPresenter: TeamListPresenter; let mockTeamDetailsPresenter: TeamDetailsPresenter; let mockTeamMembersPresenter: TeamMembersPresenter; beforeEach(() => { mockApiClient = { getAll: vi.fn(), getDetails: vi.fn(), getMembers: vi.fn(), create: vi.fn(), update: vi.fn(), getDriverTeam: vi.fn(), } as unknown as TeamsApiClient; mockTeamListPresenter = { present: vi.fn(), } as unknown as TeamListPresenter; mockTeamDetailsPresenter = { present: vi.fn(), } as unknown as TeamDetailsPresenter; mockTeamMembersPresenter = { present: vi.fn(), } as unknown as TeamMembersPresenter; service = new TeamService( mockApiClient, mockTeamListPresenter, mockTeamDetailsPresenter, mockTeamMembersPresenter ); }); describe('constructor', () => { it('should create instance with injected dependencies', () => { expect(service).toBeInstanceOf(TeamService); }); }); describe('getAllTeams', () => { it('should fetch all teams from API and transform via presenter', async () => { // Arrange const mockDto: AllTeamsDto = { teams: [ { id: 'team-1', name: 'Team Alpha', logoUrl: 'https://example.com/logo1.png', memberCount: 5, rating: 2500, }, { id: 'team-2', name: 'Team Beta', logoUrl: 'https://example.com/logo2.png', memberCount: 3, rating: 2300, }, ], }; const mockViewModels: TeamSummaryViewModel[] = [ { id: 'team-1', name: 'Team Alpha', logoUrl: 'https://example.com/logo1.png', memberCount: 5, rating: 2500, } as TeamSummaryViewModel, { id: 'team-2', name: 'Team Beta', logoUrl: 'https://example.com/logo2.png', memberCount: 3, rating: 2300, } as TeamSummaryViewModel, ]; vi.mocked(mockApiClient.getAll).mockResolvedValue(mockDto); vi.mocked(mockTeamListPresenter.present).mockReturnValue(mockViewModels); // Act const result = await service.getAllTeams(); // Assert expect(mockApiClient.getAll).toHaveBeenCalled(); expect(mockTeamListPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual(mockViewModels); }); it('should handle empty teams list', async () => { // Arrange const mockDto: AllTeamsDto = { teams: [], }; const mockViewModels: TeamSummaryViewModel[] = []; vi.mocked(mockApiClient.getAll).mockResolvedValue(mockDto); vi.mocked(mockTeamListPresenter.present).mockReturnValue(mockViewModels); // Act const result = await service.getAllTeams(); // Assert expect(mockApiClient.getAll).toHaveBeenCalled(); expect(mockTeamListPresenter.present).toHaveBeenCalledWith(mockDto); expect(result).toEqual([]); }); it('should propagate errors from API client', async () => { // Arrange const error = new Error('Failed to fetch teams'); vi.mocked(mockApiClient.getAll).mockRejectedValue(error); // Act & Assert await expect(service.getAllTeams()).rejects.toThrow('Failed to fetch teams'); expect(mockApiClient.getAll).toHaveBeenCalled(); expect(mockTeamListPresenter.present).not.toHaveBeenCalled(); }); }); describe('getTeamDetails', () => { it('should fetch team details and transform via presenter', async () => { // Arrange const teamId = 'team-123'; const currentUserId = 'user-456'; const mockDto: TeamDetailsDto = { id: teamId, name: 'Team Alpha', description: 'A competitive racing team', logoUrl: 'https://example.com/logo.png', memberCount: 5, ownerId: 'user-789', }; const mockViewModel: TeamDetailsViewModel = { id: teamId, name: 'Team Alpha', description: 'A competitive racing team', logoUrl: 'https://example.com/logo.png', memberCount: 5, ownerId: 'user-789', members: [], } as TeamDetailsViewModel; vi.mocked(mockApiClient.getDetails).mockResolvedValue(mockDto); vi.mocked(mockTeamDetailsPresenter.present).mockReturnValue(mockViewModel); // Act const result = await service.getTeamDetails(teamId, currentUserId); // Assert expect(mockApiClient.getDetails).toHaveBeenCalledWith(teamId); expect(mockTeamDetailsPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId); expect(result).toEqual(mockViewModel); }); it('should return null when team is not found', async () => { // Arrange const teamId = 'non-existent'; const currentUserId = 'user-456'; vi.mocked(mockApiClient.getDetails).mockResolvedValue(null); // Act const result = await service.getTeamDetails(teamId, currentUserId); // Assert expect(mockApiClient.getDetails).toHaveBeenCalledWith(teamId); expect(mockTeamDetailsPresenter.present).not.toHaveBeenCalled(); expect(result).toBeNull(); }); it('should propagate errors from API client', async () => { // Arrange const teamId = 'team-123'; const currentUserId = 'user-456'; const error = new Error('Failed to fetch team details'); vi.mocked(mockApiClient.getDetails).mockRejectedValue(error); // Act & Assert await expect(service.getTeamDetails(teamId, currentUserId)).rejects.toThrow('Failed to fetch team details'); expect(mockApiClient.getDetails).toHaveBeenCalledWith(teamId); expect(mockTeamDetailsPresenter.present).not.toHaveBeenCalled(); }); }); describe('getTeamMembers', () => { it('should fetch team members and transform via presenter', async () => { // Arrange const teamId = 'team-123'; const currentUserId = 'user-456'; const teamOwnerId = 'user-789'; const mockDto: TeamMembersDto = { members: [ { driverId: 'driver-1', role: 'owner', joinedAt: '2024-01-01', }, { driverId: 'driver-2', role: 'member', joinedAt: '2024-01-02', }, ], }; const mockViewModels: TeamMemberViewModel[] = [ { driverId: 'driver-1', role: 'owner', joinedAt: '2024-01-01', } as TeamMemberViewModel, { driverId: 'driver-2', role: 'member', joinedAt: '2024-01-02', } as TeamMemberViewModel, ]; vi.mocked(mockApiClient.getMembers).mockResolvedValue(mockDto); vi.mocked(mockTeamMembersPresenter.present).mockReturnValue(mockViewModels); // Act const result = await service.getTeamMembers(teamId, currentUserId, teamOwnerId); // Assert expect(mockApiClient.getMembers).toHaveBeenCalledWith(teamId); expect(mockTeamMembersPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId, teamOwnerId); expect(result).toEqual(mockViewModels); }); it('should handle empty members list', async () => { // Arrange const teamId = 'team-123'; const currentUserId = 'user-456'; const teamOwnerId = 'user-789'; const mockDto: TeamMembersDto = { members: [], }; const mockViewModels: TeamMemberViewModel[] = []; vi.mocked(mockApiClient.getMembers).mockResolvedValue(mockDto); vi.mocked(mockTeamMembersPresenter.present).mockReturnValue(mockViewModels); // Act const result = await service.getTeamMembers(teamId, currentUserId, teamOwnerId); // Assert expect(mockApiClient.getMembers).toHaveBeenCalledWith(teamId); expect(mockTeamMembersPresenter.present).toHaveBeenCalledWith(mockDto, currentUserId, teamOwnerId); expect(result).toEqual([]); }); it('should propagate errors from API client', async () => { // Arrange const teamId = 'team-123'; const currentUserId = 'user-456'; const teamOwnerId = 'user-789'; const error = new Error('Failed to fetch team members'); vi.mocked(mockApiClient.getMembers).mockRejectedValue(error); // Act & Assert await expect(service.getTeamMembers(teamId, currentUserId, teamOwnerId)).rejects.toThrow('Failed to fetch team members'); expect(mockApiClient.getMembers).toHaveBeenCalledWith(teamId); expect(mockTeamMembersPresenter.present).not.toHaveBeenCalled(); }); }); describe('createTeam', () => { it('should create a new team', async () => { // Arrange const input: CreateTeamInputDto = { name: 'New Team', description: 'A new racing team', }; const mockOutput: CreateTeamOutputDto = { id: 'team-new', name: 'New Team', success: true, }; vi.mocked(mockApiClient.create).mockResolvedValue(mockOutput); // Act const result = await service.createTeam(input); // Assert expect(mockApiClient.create).toHaveBeenCalledWith(input); expect(result).toEqual(mockOutput); }); it('should propagate errors from API client', async () => { // Arrange const input: CreateTeamInputDto = { name: 'New Team', description: 'A new racing team', }; const error = new Error('Failed to create team'); vi.mocked(mockApiClient.create).mockRejectedValue(error); // Act & Assert await expect(service.createTeam(input)).rejects.toThrow('Failed to create team'); expect(mockApiClient.create).toHaveBeenCalledWith(input); }); }); describe('updateTeam', () => { it('should update team details', async () => { // Arrange const teamId = 'team-123'; const input: UpdateTeamInputDto = { name: 'Updated Team Name', description: 'Updated description', }; const mockOutput: UpdateTeamOutputDto = { id: teamId, success: true, }; vi.mocked(mockApiClient.update).mockResolvedValue(mockOutput); // Act const result = await service.updateTeam(teamId, input); // Assert expect(mockApiClient.update).toHaveBeenCalledWith(teamId, input); expect(result).toEqual(mockOutput); }); it('should propagate errors from API client', async () => { // Arrange const teamId = 'team-123'; const input: UpdateTeamInputDto = { name: 'Updated Team Name', }; const error = new Error('Failed to update team'); vi.mocked(mockApiClient.update).mockRejectedValue(error); // Act & Assert await expect(service.updateTeam(teamId, input)).rejects.toThrow('Failed to update team'); expect(mockApiClient.update).toHaveBeenCalledWith(teamId, input); }); }); describe('getDriverTeam', () => { it('should fetch driver team', async () => { // Arrange const driverId = 'driver-123'; const mockDto: DriverTeamDto = { teamId: 'team-456', teamName: 'Team Alpha', role: 'member', }; vi.mocked(mockApiClient.getDriverTeam).mockResolvedValue(mockDto); // Act const result = await service.getDriverTeam(driverId); // Assert expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith(driverId); expect(result).toEqual(mockDto); }); it('should return null when driver has no team', async () => { // Arrange const driverId = 'driver-123'; vi.mocked(mockApiClient.getDriverTeam).mockResolvedValue(null); // Act const result = await service.getDriverTeam(driverId); // Assert expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith(driverId); expect(result).toBeNull(); }); it('should propagate errors from API client', async () => { // Arrange const driverId = 'driver-123'; const error = new Error('Failed to fetch driver team'); vi.mocked(mockApiClient.getDriverTeam).mockRejectedValue(error); // Act & Assert await expect(service.getDriverTeam(driverId)).rejects.toThrow('Failed to fetch driver team'); expect(mockApiClient.getDriverTeam).toHaveBeenCalledWith(driverId); }); }); });