website service tests
This commit is contained in:
132
apps/website/lib/services/leagues/LeagueSettingsService.test.ts
Normal file
132
apps/website/lib/services/leagues/LeagueSettingsService.test.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { describe, it, expect, vi, Mocked } from 'vitest';
|
||||
import { LeagueSettingsService } from './LeagueSettingsService';
|
||||
import { LeaguesApiClient } from '../../api/leagues/LeaguesApiClient';
|
||||
import { DriversApiClient } from '../../api/drivers/DriversApiClient';
|
||||
import { LeagueSettingsViewModel } from '@/lib/view-models/LeagueSettingsViewModel';
|
||||
|
||||
describe('LeagueSettingsService', () => {
|
||||
let mockLeaguesApiClient: Mocked<LeaguesApiClient>;
|
||||
let mockDriversApiClient: Mocked<DriversApiClient>;
|
||||
let service: LeagueSettingsService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLeaguesApiClient = {
|
||||
getAllWithCapacity: vi.fn(),
|
||||
getLeagueConfig: vi.fn(),
|
||||
getScoringPresets: vi.fn(),
|
||||
getMemberships: vi.fn(),
|
||||
transferOwnership: vi.fn(),
|
||||
} as unknown as Mocked<LeaguesApiClient>;
|
||||
|
||||
mockDriversApiClient = {
|
||||
getLeaderboard: vi.fn(),
|
||||
getDriver: vi.fn(),
|
||||
} as unknown as Mocked<DriversApiClient>;
|
||||
|
||||
service = new LeagueSettingsService(mockLeaguesApiClient, mockDriversApiClient);
|
||||
});
|
||||
|
||||
describe('getLeagueSettings', () => {
|
||||
it('should return league settings with all data', async () => {
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// Mock getAllWithCapacity
|
||||
mockLeaguesApiClient.getAllWithCapacity.mockResolvedValue({
|
||||
leagues: [
|
||||
{ id: leagueId, name: 'Test League', ownerId: 'owner-123', capacity: 20, currentMembers: 10 },
|
||||
],
|
||||
});
|
||||
|
||||
// Mock getLeagueConfig
|
||||
mockLeaguesApiClient.getLeagueConfig.mockResolvedValue({
|
||||
config: { someConfig: 'value' },
|
||||
});
|
||||
|
||||
// Mock getScoringPresets
|
||||
mockLeaguesApiClient.getScoringPresets.mockResolvedValue({
|
||||
presets: [{ id: 'preset-1', name: 'Preset 1' }],
|
||||
});
|
||||
|
||||
// Mock getLeaderboard
|
||||
mockDriversApiClient.getLeaderboard.mockResolvedValue({
|
||||
drivers: [
|
||||
{ id: 'owner-123', name: 'Owner Driver', rating: 1500, rank: 10 },
|
||||
{ id: 'member-1', name: 'Member 1', rating: 1400, rank: 20 },
|
||||
],
|
||||
});
|
||||
|
||||
// Mock getDriver for owner
|
||||
mockDriversApiClient.getDriver.mockResolvedValue({
|
||||
id: 'owner-123',
|
||||
name: 'Owner Driver',
|
||||
avatarUrl: 'https://example.com/avatar.jpg',
|
||||
country: 'US',
|
||||
});
|
||||
|
||||
// Mock getMemberships
|
||||
mockLeaguesApiClient.getMemberships.mockResolvedValue({
|
||||
members: [
|
||||
{ driverId: 'member-1', role: 'member' },
|
||||
],
|
||||
});
|
||||
|
||||
const result = await service.getLeagueSettings(leagueId);
|
||||
|
||||
expect(result).toBeInstanceOf(LeagueSettingsViewModel);
|
||||
expect(result.league.id).toBe(leagueId);
|
||||
expect(result.league.name).toBe('Test League');
|
||||
expect(result.league.ownerId).toBe('owner-123');
|
||||
expect(result.presets).toHaveLength(1);
|
||||
expect(result.owner).toBeDefined();
|
||||
expect(result.members).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should return null when league not found', async () => {
|
||||
const leagueId = 'non-existent-league';
|
||||
|
||||
mockLeaguesApiClient.getAllWithCapacity.mockResolvedValue({
|
||||
leagues: [],
|
||||
});
|
||||
|
||||
const result = await service.getLeagueSettings(leagueId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
const leagueId = 'league-123';
|
||||
|
||||
mockLeaguesApiClient.getAllWithCapacity.mockRejectedValue(new Error('API error'));
|
||||
|
||||
const result = await service.getLeagueSettings(leagueId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transferOwnership', () => {
|
||||
it('should call apiClient.transferOwnership and return success', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const currentOwnerId = 'owner-123';
|
||||
const newOwnerId = 'new-owner-456';
|
||||
|
||||
mockLeaguesApiClient.transferOwnership.mockResolvedValue({ success: true });
|
||||
|
||||
const result = await service.transferOwnership(leagueId, currentOwnerId, newOwnerId);
|
||||
|
||||
expect(mockLeaguesApiClient.transferOwnership).toHaveBeenCalledWith(leagueId, currentOwnerId, newOwnerId);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw error when apiClient.transferOwnership fails', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const currentOwnerId = 'owner-123';
|
||||
const newOwnerId = 'new-owner-456';
|
||||
|
||||
const error = new Error('API call failed');
|
||||
mockLeaguesApiClient.transferOwnership.mockRejectedValue(error);
|
||||
|
||||
await expect(service.transferOwnership(leagueId, currentOwnerId, newOwnerId)).rejects.toThrow('API call failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { describe, it, expect, vi, Mocked } from 'vitest';
|
||||
import { LeagueStewardingService } from './LeagueStewardingService';
|
||||
import { RaceService } from '../races/RaceService';
|
||||
import { ProtestService } from '../protests/ProtestService';
|
||||
import { PenaltyService } from '../penalties/PenaltyService';
|
||||
import { DriverService } from '../drivers/DriverService';
|
||||
import { LeagueMembershipService } from './LeagueMembershipService';
|
||||
import { LeagueStewardingViewModel } from '../../view-models/LeagueStewardingViewModel';
|
||||
|
||||
describe('LeagueStewardingService', () => {
|
||||
let mockRaceService: Mocked<RaceService>;
|
||||
let mockProtestService: Mocked<ProtestService>;
|
||||
let mockPenaltyService: Mocked<PenaltyService>;
|
||||
let mockDriverService: Mocked<DriverService>;
|
||||
let mockLeagueMembershipService: Mocked<LeagueMembershipService>;
|
||||
let service: LeagueStewardingService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRaceService = {
|
||||
findByLeagueId: vi.fn(),
|
||||
} as Mocked<RaceService>;
|
||||
|
||||
mockProtestService = {
|
||||
findByRaceId: vi.fn(),
|
||||
} as Mocked<ProtestService>;
|
||||
|
||||
mockPenaltyService = {
|
||||
findByRaceId: vi.fn(),
|
||||
} as Mocked<PenaltyService>;
|
||||
|
||||
mockDriverService = {
|
||||
findByIds: vi.fn(),
|
||||
} as Mocked<DriverService>;
|
||||
|
||||
mockLeagueMembershipService = {} as Mocked<LeagueMembershipService>;
|
||||
|
||||
service = new LeagueStewardingService(
|
||||
mockRaceService,
|
||||
mockProtestService,
|
||||
mockPenaltyService,
|
||||
mockDriverService,
|
||||
mockLeagueMembershipService
|
||||
);
|
||||
});
|
||||
|
||||
describe('getLeagueStewardingData', () => {
|
||||
it('should return stewarding data with all races and their protests/penalties', async () => {
|
||||
const leagueId = 'league-123';
|
||||
|
||||
// Mock races
|
||||
mockRaceService.findByLeagueId.mockResolvedValue([
|
||||
{ id: 'race-1', track: 'Monza', scheduledAt: '2023-10-01T10:00:00Z' },
|
||||
{ id: 'race-2', track: 'Silverstone', scheduledAt: '2023-09-15T10:00:00Z' },
|
||||
]);
|
||||
|
||||
// Mock protests for race-1
|
||||
mockProtestService.findByRaceId.mockImplementation(async (raceId) => {
|
||||
if (raceId === 'race-1') {
|
||||
return [
|
||||
{ id: 'protest-1', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', status: 'pending' },
|
||||
{ id: 'protest-2', protestingDriverId: 'driver-3', accusedDriverId: 'driver-4', status: 'upheld' },
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Mock penalties for race-1
|
||||
mockPenaltyService.findByRaceId.mockImplementation(async (raceId) => {
|
||||
if (raceId === 'race-1') {
|
||||
return [
|
||||
{ id: 'penalty-1', driverId: 'driver-2', type: 'time', value: 5, reason: 'Incident' },
|
||||
];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
// Mock driver service
|
||||
mockDriverService.findByIds.mockResolvedValue([
|
||||
{ id: 'driver-1', name: 'Driver 1' },
|
||||
{ id: 'driver-2', name: 'Driver 2' },
|
||||
{ id: 'driver-3', name: 'Driver 3' },
|
||||
{ id: 'driver-4', name: 'Driver 4' },
|
||||
]);
|
||||
|
||||
const result = await service.getLeagueStewardingData(leagueId);
|
||||
|
||||
expect(result).toBeInstanceOf(LeagueStewardingViewModel);
|
||||
expect(result.racesWithData).toHaveLength(2);
|
||||
expect(result.totalPending).toBe(1);
|
||||
expect(result.totalResolved).toBe(1);
|
||||
expect(result.totalPenalties).toBe(1);
|
||||
expect(result.pendingRaces).toHaveLength(1);
|
||||
expect(result.historyRaces).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should handle empty races array', async () => {
|
||||
const leagueId = 'league-123';
|
||||
|
||||
mockRaceService.findByLeagueId.mockResolvedValue([]);
|
||||
mockProtestService.findByRaceId.mockResolvedValue([]);
|
||||
mockPenaltyService.findByRaceId.mockResolvedValue([]);
|
||||
mockDriverService.findByIds.mockResolvedValue([]);
|
||||
|
||||
const result = await service.getLeagueStewardingData(leagueId);
|
||||
|
||||
expect(result.racesWithData).toHaveLength(0);
|
||||
expect(result.totalPending).toBe(0);
|
||||
expect(result.totalResolved).toBe(0);
|
||||
expect(result.totalPenalties).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reviewProtest', () => {
|
||||
it('should call protestService.reviewProtest', async () => {
|
||||
const input = {
|
||||
protestId: 'protest-123',
|
||||
stewardId: 'steward-456',
|
||||
decision: 'upheld',
|
||||
decisionNotes: 'Test notes',
|
||||
};
|
||||
|
||||
mockProtestService.reviewProtest = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
await service.reviewProtest(input);
|
||||
|
||||
expect(mockProtestService.reviewProtest).toHaveBeenCalledWith(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyPenalty', () => {
|
||||
it('should call penaltyService.applyPenalty', async () => {
|
||||
const input = {
|
||||
driverId: 'driver-123',
|
||||
raceId: 'race-456',
|
||||
type: 'time',
|
||||
value: 10,
|
||||
reason: 'Test reason',
|
||||
};
|
||||
|
||||
mockPenaltyService.applyPenalty = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
await service.applyPenalty(input);
|
||||
|
||||
expect(mockPenaltyService.applyPenalty).toHaveBeenCalledWith(input);
|
||||
});
|
||||
});
|
||||
});
|
||||
123
apps/website/lib/services/leagues/LeagueWalletService.test.ts
Normal file
123
apps/website/lib/services/leagues/LeagueWalletService.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { describe, it, expect, vi, Mocked } from 'vitest';
|
||||
import { LeagueWalletService } from './LeagueWalletService';
|
||||
import { WalletsApiClient } from '../../api/wallets/WalletsApiClient';
|
||||
import { LeagueWalletViewModel } from '@/lib/view-models/LeagueWalletViewModel';
|
||||
|
||||
describe('LeagueWalletService', () => {
|
||||
let mockApiClient: Mocked<WalletsApiClient>;
|
||||
let service: LeagueWalletService;
|
||||
|
||||
beforeEach(() => {
|
||||
mockApiClient = {
|
||||
getLeagueWallet: vi.fn(),
|
||||
withdrawFromLeagueWallet: vi.fn(),
|
||||
} as unknown as Mocked<WalletsApiClient>;
|
||||
|
||||
service = new LeagueWalletService(mockApiClient);
|
||||
});
|
||||
|
||||
describe('getWalletForLeague', () => {
|
||||
it('should call apiClient.getLeagueWallet and return LeagueWalletViewModel', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const mockDto = {
|
||||
balance: 1000,
|
||||
currency: 'USD',
|
||||
totalRevenue: 5000,
|
||||
totalFees: 1000,
|
||||
totalWithdrawals: 2000,
|
||||
pendingPayouts: 500,
|
||||
canWithdraw: true,
|
||||
transactions: [
|
||||
{
|
||||
id: 'txn-1',
|
||||
type: 'sponsorship' as const,
|
||||
description: 'Sponsorship payment',
|
||||
amount: 100,
|
||||
fee: 10,
|
||||
netAmount: 90,
|
||||
date: '2023-10-01T10:00:00Z',
|
||||
status: 'completed' as const,
|
||||
reference: 'ref-1',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockApiClient.getLeagueWallet.mockResolvedValue(mockDto);
|
||||
|
||||
const result = await service.getWalletForLeague(leagueId);
|
||||
|
||||
expect(mockApiClient.getLeagueWallet).toHaveBeenCalledWith(leagueId);
|
||||
expect(result).toBeInstanceOf(LeagueWalletViewModel);
|
||||
expect(result.balance).toBe(1000);
|
||||
expect(result.currency).toBe('USD');
|
||||
expect(result.transactions).toHaveLength(1);
|
||||
expect(result.formattedBalance).toBe('$1000.00');
|
||||
});
|
||||
|
||||
it('should throw error when apiClient.getLeagueWallet fails', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const error = new Error('API call failed');
|
||||
mockApiClient.getLeagueWallet.mockRejectedValue(error);
|
||||
|
||||
await expect(service.getWalletForLeague(leagueId)).rejects.toThrow('API call failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('withdraw', () => {
|
||||
it('should call apiClient.withdrawFromLeagueWallet with correct parameters', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const amount = 500;
|
||||
const currency = 'USD';
|
||||
const seasonId = 'season-456';
|
||||
const destinationAccount = 'account-789';
|
||||
|
||||
const mockResponse = { success: true, message: 'Withdrawal successful' };
|
||||
mockApiClient.withdrawFromLeagueWallet.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await service.withdraw(leagueId, amount, currency, seasonId, destinationAccount);
|
||||
|
||||
expect(mockApiClient.withdrawFromLeagueWallet).toHaveBeenCalledWith(leagueId, {
|
||||
amount,
|
||||
currency,
|
||||
seasonId,
|
||||
destinationAccount,
|
||||
});
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should throw error when apiClient.withdrawFromLeagueWallet fails', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const amount = 500;
|
||||
const currency = 'USD';
|
||||
const seasonId = 'season-456';
|
||||
const destinationAccount = 'account-789';
|
||||
|
||||
const error = new Error('Withdrawal failed');
|
||||
mockApiClient.withdrawFromLeagueWallet.mockRejectedValue(error);
|
||||
|
||||
await expect(service.withdraw(leagueId, amount, currency, seasonId, destinationAccount)).rejects.toThrow('Withdrawal failed');
|
||||
});
|
||||
|
||||
it('should block multiple rapid calls due to throttle', async () => {
|
||||
const leagueId = 'league-123';
|
||||
const amount = 500;
|
||||
const currency = 'USD';
|
||||
const seasonId = 'season-456';
|
||||
const destinationAccount = 'account-789';
|
||||
|
||||
const mockResponse = { success: true };
|
||||
mockApiClient.withdrawFromLeagueWallet.mockResolvedValue(mockResponse);
|
||||
|
||||
// First call should succeed
|
||||
await service.withdraw(leagueId, amount, currency, seasonId, destinationAccount);
|
||||
|
||||
// Reset mock
|
||||
mockApiClient.withdrawFromLeagueWallet.mockClear();
|
||||
|
||||
// Immediate second call should be blocked by throttle and throw error
|
||||
await expect(service.withdraw(leagueId, amount, currency, seasonId, destinationAccount)).rejects.toThrow('Request blocked due to rate limiting');
|
||||
|
||||
expect(mockApiClient.withdrawFromLeagueWallet).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { describe, it, expect, vi, Mocked } from 'vitest';
|
||||
import { LeagueWizardService } from './LeagueWizardService';
|
||||
import { LeagueWizardCommandModel } from '@/lib/command-models/leagues/LeagueWizardCommandModel';
|
||||
import { apiClient } from '@/lib/apiClient';
|
||||
|
||||
// Mock the apiClient
|
||||
vi.mock('@/lib/apiClient', () => ({
|
||||
apiClient: {
|
||||
leagues: {
|
||||
create: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe('LeagueWizardService', () => {
|
||||
describe('createLeague', () => {
|
||||
it('should call apiClient.leagues.create with correct command', async () => {
|
||||
const form = {
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
toCreateLeagueCommand: vi.fn().mockReturnValue({
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
ownerId: 'owner-123',
|
||||
}),
|
||||
} as unknown as LeagueWizardCommandModel;
|
||||
|
||||
const ownerId = 'owner-123';
|
||||
const mockOutput = { leagueId: 'new-league-id', success: true };
|
||||
|
||||
(apiClient.leagues.create as any).mockResolvedValue(mockOutput);
|
||||
|
||||
const result = await LeagueWizardService.createLeague(form, ownerId);
|
||||
|
||||
expect(form.toCreateLeagueCommand).toHaveBeenCalledWith(ownerId);
|
||||
expect(apiClient.leagues.create).toHaveBeenCalledWith({
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
ownerId: 'owner-123',
|
||||
});
|
||||
expect(result).toEqual(mockOutput);
|
||||
});
|
||||
|
||||
it('should throw error when apiClient.leagues.create fails', async () => {
|
||||
const form = {
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
toCreateLeagueCommand: vi.fn().mockReturnValue({
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
ownerId: 'owner-123',
|
||||
}),
|
||||
} as unknown as LeagueWizardCommandModel;
|
||||
|
||||
const ownerId = 'owner-123';
|
||||
const error = new Error('API call failed');
|
||||
|
||||
(apiClient.leagues.create as Mocked<typeof apiClient.leagues.create>).mockRejectedValue(error);
|
||||
|
||||
await expect(LeagueWizardService.createLeague(form, ownerId)).rejects.toThrow('API call failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createLeagueFromConfig', () => {
|
||||
it('should call createLeague with same parameters', async () => {
|
||||
const form = {
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
toCreateLeagueCommand: vi.fn().mockReturnValue({
|
||||
name: 'Test League',
|
||||
description: 'A test league',
|
||||
ownerId: 'owner-123',
|
||||
}),
|
||||
} as unknown as LeagueWizardCommandModel;
|
||||
|
||||
const ownerId = 'owner-123';
|
||||
const mockOutput = { leagueId: 'new-league-id', success: true };
|
||||
|
||||
(apiClient.leagues.create as Mocked<typeof apiClient.leagues.create>).mockResolvedValue(mockOutput);
|
||||
|
||||
const result = await LeagueWizardService.createLeagueFromConfig(form, ownerId);
|
||||
|
||||
expect(apiClient.leagues.create).toHaveBeenCalled();
|
||||
expect(result).toEqual(mockOutput);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user