website refactor
This commit is contained in:
257
apps/api/src/domain/league/LeagueService.endpoints.test.ts
Normal file
257
apps/api/src/domain/league/LeagueService.endpoints.test.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import { requestContextMiddleware } from '@adapters/http/RequestContext';
|
||||
import { Result } from '@core/shared/domain/Result';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { LeagueService } from './LeagueService';
|
||||
|
||||
async function withUserId<T>(userId: string, fn: () => Promise<T>): Promise<T> {
|
||||
const req = { user: { userId } };
|
||||
const res = {};
|
||||
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
requestContextMiddleware(req as never, res as never, () => {
|
||||
fn().then(resolve, reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('LeagueService - All Endpoints', () => {
|
||||
it('covers all league endpoint happy paths and error branches', async () => {
|
||||
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
||||
|
||||
const ok = async () => Result.ok(undefined);
|
||||
const err = async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never);
|
||||
|
||||
// Discovery use cases
|
||||
const getAllLeaguesWithCapacityUseCase = { execute: vi.fn(async () => Result.ok({ leagues: [] })) };
|
||||
const getAllLeaguesWithCapacityAndScoringUseCase = { execute: vi.fn(ok) };
|
||||
const getTotalLeaguesUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
// Detail use cases
|
||||
const getLeagueOwnerSummaryUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueSeasonsUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueStatsUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueMembershipsUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
// Schedule use case
|
||||
const getLeagueScheduleUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
// Standings use case
|
||||
const getLeagueStandingsUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
// Other use cases (for completeness)
|
||||
const getLeagueFullConfigUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueScoringConfigUseCase = { execute: vi.fn(ok) };
|
||||
const listLeagueScoringPresetsUseCase = { execute: vi.fn(ok) };
|
||||
const joinLeagueUseCase = { execute: vi.fn(ok) };
|
||||
const transferLeagueOwnershipUseCase = { execute: vi.fn(ok) };
|
||||
const createLeagueWithSeasonAndScoringUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueJoinRequestsUseCase = { execute: vi.fn(ok) };
|
||||
const approveLeagueJoinRequestUseCase = { execute: vi.fn(ok) };
|
||||
const rejectLeagueJoinRequestUseCase = { execute: vi.fn(ok) };
|
||||
const removeLeagueMemberUseCase = { execute: vi.fn(ok) };
|
||||
const updateLeagueMemberRoleUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueProtestsUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueAdminPermissionsUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueWalletUseCase = { execute: vi.fn(ok) };
|
||||
const withdrawFromLeagueWalletUseCase = { execute: vi.fn(ok) };
|
||||
const getSeasonSponsorshipsUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
const getLeagueRosterMembersUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueRosterJoinRequestsUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
// Schedule mutation use cases
|
||||
const createLeagueSeasonScheduleRaceUseCase = { execute: vi.fn(ok) };
|
||||
const updateLeagueSeasonScheduleRaceUseCase = { execute: vi.fn(ok) };
|
||||
const deleteLeagueSeasonScheduleRaceUseCase = { execute: vi.fn(ok) };
|
||||
const publishLeagueSeasonScheduleUseCase = { execute: vi.fn(ok) };
|
||||
const unpublishLeagueSeasonScheduleUseCase = { execute: vi.fn(ok) };
|
||||
|
||||
// Presenters
|
||||
const allLeaguesWithCapacityPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ leagues: [] })) };
|
||||
const allLeaguesWithCapacityAndScoringPresenter = {
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ({ leagues: [], totalCount: 0 })),
|
||||
};
|
||||
const leagueStandingsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ standings: [] })) };
|
||||
const leagueProtestsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ protests: [] })) };
|
||||
const seasonSponsorshipsPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ sponsorships: [] })) };
|
||||
const leagueScoringPresetsPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ presets: [] })) };
|
||||
const approveLeagueJoinRequestPresenter = {
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ({ success: true }))
|
||||
};
|
||||
const createLeaguePresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ id: 'l1' })) };
|
||||
const getLeagueAdminPermissionsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ canManage: true })) };
|
||||
const getLeagueMembershipsPresenter = {
|
||||
reset: vi.fn(),
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ({ memberships: { members: [] } })),
|
||||
};
|
||||
|
||||
const getLeagueRosterMembersPresenter = {
|
||||
reset: vi.fn(),
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ([])),
|
||||
};
|
||||
|
||||
const getLeagueRosterJoinRequestsPresenter = {
|
||||
reset: vi.fn(),
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ([])),
|
||||
};
|
||||
const getLeagueOwnerSummaryPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ driver: { id: 'd1', iracingId: '12345', name: 'Driver', country: 'US', joinedAt: '2024-01-01T00:00:00Z' }, rating: 1500, rank: 10 })) };
|
||||
const getLeagueSeasonsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ([])) };
|
||||
const joinLeaguePresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||
const leagueSchedulePresenter = { reset: vi.fn(), present: vi.fn(), getViewModel: vi.fn(() => ({ seasonId: 'season-1', published: false, races: [] })) };
|
||||
const leagueStatsPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ totalMembers: 0, totalRaces: 0, averageRating: 0 })) };
|
||||
const rejectLeagueJoinRequestPresenter = {
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ({ success: true }))
|
||||
};
|
||||
const removeLeagueMemberPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||
const totalLeaguesPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ totalLeagues: 0 })) };
|
||||
const transferLeagueOwnershipPresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||
const updateLeagueMemberRolePresenter = { present: vi.fn(), getViewModel: vi.fn(() => ({ success: true })) };
|
||||
const leagueConfigPresenter = {
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ({ form: {} }))
|
||||
};
|
||||
const leagueScoringConfigPresenter = {
|
||||
present: vi.fn(),
|
||||
getViewModel: vi.fn(() => ({ config: {} }))
|
||||
};
|
||||
const getLeagueWalletPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ balance: 0 })) };
|
||||
const withdrawFromLeagueWalletPresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true })) };
|
||||
const leagueJoinRequestsPresenter = { reset: vi.fn(), present: vi.fn(), getViewModel: vi.fn(() => ({ joinRequests: [] })) };
|
||||
|
||||
const createLeagueSeasonScheduleRacePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ raceId: 'race-1' })) };
|
||||
const updateLeagueSeasonScheduleRacePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true })) };
|
||||
const deleteLeagueSeasonScheduleRacePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true })) };
|
||||
const publishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: true })) };
|
||||
const unpublishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: false })) };
|
||||
|
||||
const service = new (LeagueService as unknown as { new (...args: never[]): LeagueService })(
|
||||
getAllLeaguesWithCapacityUseCase as never,
|
||||
getAllLeaguesWithCapacityAndScoringUseCase as never,
|
||||
getLeagueStandingsUseCase as never,
|
||||
getLeagueStatsUseCase as never,
|
||||
getLeagueFullConfigUseCase as never,
|
||||
getLeagueScoringConfigUseCase as never,
|
||||
listLeagueScoringPresetsUseCase as never,
|
||||
joinLeagueUseCase as never,
|
||||
transferLeagueOwnershipUseCase as never,
|
||||
createLeagueWithSeasonAndScoringUseCase as never,
|
||||
getTotalLeaguesUseCase as never,
|
||||
getLeagueJoinRequestsUseCase as never,
|
||||
approveLeagueJoinRequestUseCase as never,
|
||||
rejectLeagueJoinRequestUseCase as never,
|
||||
removeLeagueMemberUseCase as never,
|
||||
updateLeagueMemberRoleUseCase as never,
|
||||
getLeagueOwnerSummaryUseCase as never,
|
||||
getLeagueProtestsUseCase as never,
|
||||
getLeagueSeasonsUseCase as never,
|
||||
getLeagueMembershipsUseCase as never,
|
||||
getLeagueScheduleUseCase as never,
|
||||
getLeagueAdminPermissionsUseCase as never,
|
||||
getLeagueWalletUseCase as never,
|
||||
withdrawFromLeagueWalletUseCase as never,
|
||||
getSeasonSponsorshipsUseCase as never,
|
||||
createLeagueSeasonScheduleRaceUseCase as never,
|
||||
updateLeagueSeasonScheduleRaceUseCase as never,
|
||||
deleteLeagueSeasonScheduleRaceUseCase as never,
|
||||
publishLeagueSeasonScheduleUseCase as never,
|
||||
unpublishLeagueSeasonScheduleUseCase as never,
|
||||
logger as never,
|
||||
allLeaguesWithCapacityPresenter as never,
|
||||
allLeaguesWithCapacityAndScoringPresenter as never,
|
||||
leagueStandingsPresenter as never,
|
||||
leagueProtestsPresenter as never,
|
||||
seasonSponsorshipsPresenter as never,
|
||||
leagueScoringPresetsPresenter as never,
|
||||
approveLeagueJoinRequestPresenter as never,
|
||||
createLeaguePresenter as never,
|
||||
getLeagueAdminPermissionsPresenter as never,
|
||||
getLeagueMembershipsPresenter as never,
|
||||
getLeagueOwnerSummaryPresenter as never,
|
||||
getLeagueSeasonsPresenter as never,
|
||||
joinLeaguePresenter as never,
|
||||
leagueSchedulePresenter as never,
|
||||
leagueStatsPresenter as never,
|
||||
rejectLeagueJoinRequestPresenter as never,
|
||||
removeLeagueMemberPresenter as never,
|
||||
totalLeaguesPresenter as never,
|
||||
transferLeagueOwnershipPresenter as never,
|
||||
updateLeagueMemberRolePresenter as never,
|
||||
leagueConfigPresenter as never,
|
||||
leagueScoringConfigPresenter as never,
|
||||
getLeagueWalletPresenter as never,
|
||||
withdrawFromLeagueWalletPresenter as never,
|
||||
leagueJoinRequestsPresenter as never,
|
||||
createLeagueSeasonScheduleRacePresenter as never,
|
||||
updateLeagueSeasonScheduleRacePresenter as never,
|
||||
deleteLeagueSeasonScheduleRacePresenter as never,
|
||||
publishLeagueSeasonSchedulePresenter as never,
|
||||
unpublishLeagueSeasonSchedulePresenter as never,
|
||||
|
||||
// Roster admin read delegation (added for strict TDD)
|
||||
getLeagueRosterMembersUseCase as never,
|
||||
getLeagueRosterJoinRequestsUseCase as never,
|
||||
getLeagueRosterMembersPresenter as never,
|
||||
getLeagueRosterJoinRequestsPresenter as never,
|
||||
);
|
||||
|
||||
// Discovery endpoints
|
||||
await expect(service.getAllLeaguesWithCapacity()).resolves.toEqual({ leagues: [] });
|
||||
await expect(service.getAllLeaguesWithCapacityAndScoring()).resolves.toEqual({ leagues: [], totalCount: 0 });
|
||||
await expect(service.getTotalLeagues()).resolves.toEqual({ totalLeagues: 0 });
|
||||
|
||||
// Detail endpoints
|
||||
await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as never)).resolves.toEqual({ driver: { id: 'd1', iracingId: '12345', name: 'Driver', country: 'US', joinedAt: '2024-01-01T00:00:00Z' }, rating: 1500, rank: 10 });
|
||||
await expect(service.getLeagueSeasons({ leagueId: 'l1' } as never)).resolves.toEqual([]);
|
||||
await expect(service.getLeagueStats('l1')).resolves.toEqual({ totalMembers: 0, totalRaces: 0, averageRating: 0 });
|
||||
await expect(service.getLeagueMemberships('l1')).resolves.toEqual({ members: [] });
|
||||
|
||||
// Schedule endpoint
|
||||
await expect(service.getLeagueSchedule('l1')).resolves.toEqual({ seasonId: 'season-1', published: false, races: [] });
|
||||
expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
|
||||
getLeagueScheduleUseCase.execute.mockClear();
|
||||
await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as never)).resolves.toEqual({
|
||||
seasonId: 'season-1',
|
||||
published: false,
|
||||
races: [],
|
||||
});
|
||||
expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 'season-x' });
|
||||
|
||||
// Standings endpoint
|
||||
await expect(service.getLeagueStandings('l1')).resolves.toEqual({ standings: [] });
|
||||
|
||||
// Error branches: use case returns error result
|
||||
getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never));
|
||||
await expect(service.getAllLeaguesWithCapacity()).rejects.toThrow('REPOSITORY_ERROR');
|
||||
|
||||
getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(
|
||||
Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never)
|
||||
);
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull();
|
||||
|
||||
getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => {
|
||||
throw new Error('boom');
|
||||
});
|
||||
await expect(service.getLeagueScoringConfig('l1')).resolves.toBeNull();
|
||||
|
||||
// Cover non-Error throw branches for logger.error wrapping
|
||||
getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(
|
||||
Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never)
|
||||
);
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull();
|
||||
|
||||
getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => {
|
||||
throw 'boom';
|
||||
});
|
||||
await expect(service.getLeagueScoringConfig('l1')).resolves.toBeNull();
|
||||
|
||||
// keep lint happy (ensures err() used)
|
||||
await err();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user