Files
gridpilot.gg/apps/website/lib/services/teams/TeamJoinService.test.ts
2025-12-17 22:17:02 +01:00

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');
});
});
});