website refactor

This commit is contained in:
2026-01-16 12:55:48 +01:00
parent 0208334c59
commit 20a42c52fd
83 changed files with 1610 additions and 1238 deletions

View File

@@ -37,7 +37,7 @@ describe('LeagueController', () => {
it('getTotalLeagues should return total leagues', async () => {
const mockResult = { totalLeagues: 1 };
leagueService.getTotalLeagues.mockResolvedValue(mockResult as any);
leagueService.getTotalLeagues.mockResolvedValue(mockResult as never);
const result = await controller.getTotalLeagues();
@@ -47,7 +47,7 @@ describe('LeagueController', () => {
it('getAllLeaguesWithCapacity should return leagues and totalCount', async () => {
const mockResult = { leagues: [], totalCount: 0 };
leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as any);
leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as never);
const result = await controller.getAllLeaguesWithCapacity();
@@ -57,7 +57,7 @@ describe('LeagueController', () => {
it('getLeagueStandings should return standings', async () => {
const mockResult = { standings: [] };
leagueService.getLeagueStandings.mockResolvedValue(mockResult as any);
leagueService.getLeagueStandings.mockResolvedValue(mockResult as never);
const result = await controller.getLeagueStandings('league-1');
@@ -66,7 +66,7 @@ describe('LeagueController', () => {
});
describe('auth guards (HTTP)', () => {
let app: any;
let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null),
@@ -105,9 +105,9 @@ describe('LeagueController', () => {
const reflector = new Reflector();
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as never),
);
await app.init();

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('League roster admin read (HTTP, league-scoped)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('League roster admin read (HTTP, league-scoped)', () => {
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as never),
);
await app.init();
@@ -105,7 +105,7 @@ describe('League roster admin read (HTTP, league-scoped)', () => {
expect(res.body).toEqual(expect.any(Array));
expect(res.body.length).toBeGreaterThan(0);
const first = res.body[0] as any;
const first = res.body[0] as unknown as { driver: { id: string, name: string, country: string }, role: string };
expect(first).toMatchObject({
driverId: expect.any(String),
role: expect.any(String),
@@ -133,8 +133,8 @@ describe('League roster admin read (HTTP, league-scoped)', () => {
// Seed data may or may not include join requests for a given league.
// Validate shape on first item if present.
if ((res.body as any[]).length > 0) {
const first = (res.body as any[])[0];
if ((res.body as never[]).length > 0) {
const first = (res.body as unknown as { message?: string }[])[0];
expect(first).toMatchObject({
id: expect.any(String),
leagueId: expect.any(String),
@@ -146,8 +146,10 @@ describe('League roster admin read (HTTP, league-scoped)', () => {
},
});
if (first.message !== undefined) {
expect(first.message).toEqual(expect.any(String));
if (first) {
if (first.message !== undefined) {
expect(first.message).toEqual(expect.any(String));
}
}
}
});

View File

@@ -25,7 +25,7 @@ import { JoinRequest } from '@core/racing/domain/entities/JoinRequest';
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
describe('League roster join request mutations (HTTP)', () => {
let app: any;
let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null),
@@ -123,15 +123,16 @@ describe('League roster join request mutations (HTTP)', () => {
app = module.createNestApplication();
// Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner().
app.use(requestContextMiddleware as any);
app.use(requestContextMiddleware as never);
// Test-only auth injection: emulate an authenticated session by setting request.user.
app.use((req: any, _res: any, next: any) => {
const userId = req.headers['x-test-user-id'];
app.use((req: unknown, _res: unknown, next: unknown) => {
const r = req as { headers: Record<string, string>, user?: { userId: string } };
const userId = r.headers['x-test-user-id'];
if (typeof userId === 'string' && userId.length > 0) {
req.user = { userId };
r.user = { userId };
}
next();
(next as () => void)();
});
app.useGlobalPipes(
@@ -144,8 +145,8 @@ describe('League roster join request mutations (HTTP)', () => {
const reflector = new Reflector();
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as never),
);
await app.init();
@@ -202,7 +203,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200);
expect(Array.isArray(joinRequests.body)).toBe(true);
expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeUndefined();
expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeUndefined();
const members = await request(app.getHttpServer())
.get('/leagues/league-1/admin/roster/members')
@@ -210,7 +211,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200);
expect(Array.isArray(members.body)).toBe(true);
expect(members.body.some((m: any) => m.driverId === 'driver-2')).toBe(true);
expect(members.body.some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(true);
});
it('reject removes request only; roster reads reflect changes', async () => {
@@ -232,7 +233,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200);
expect(Array.isArray(joinRequests.body)).toBe(true);
expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeUndefined();
expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeUndefined();
const members = await request(app.getHttpServer())
.get('/leagues/league-1/admin/roster/members')
@@ -240,7 +241,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200);
expect(Array.isArray(members.body)).toBe(true);
expect(members.body.some((m: any) => m.driverId === 'driver-2')).toBe(false);
expect(members.body.some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(false);
});
it('approve returns error when league is full and keeps request pending', async () => {
@@ -264,6 +265,6 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200);
expect(Array.isArray(joinRequests.body)).toBe(true);
expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeDefined();
expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeDefined();
});
});

View File

@@ -24,7 +24,7 @@ import { Driver } from '@core/racing/domain/entities/Driver';
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
describe('League roster member mutations (HTTP)', () => {
let app: any;
let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null),
@@ -98,15 +98,16 @@ describe('League roster member mutations (HTTP)', () => {
app = module.createNestApplication();
// Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner().
app.use(requestContextMiddleware as any);
app.use(requestContextMiddleware as never);
// Test-only auth injection: emulate an authenticated session by setting request.user.
app.use((req: any, _res: any, next: any) => {
const userId = req.headers['x-test-user-id'];
app.use((req: unknown, _res: unknown, next: unknown) => {
const r = req as { headers: Record<string, string>, user?: { userId: string } };
const userId = r.headers['x-test-user-id'];
if (typeof userId === 'string' && userId.length > 0) {
req.user = { userId };
r.user = { userId };
}
next();
(next as () => void)();
});
app.useGlobalPipes(
@@ -119,8 +120,8 @@ describe('League roster member mutations (HTTP)', () => {
const reflector = new Reflector();
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as never),
);
await app.init();
@@ -173,9 +174,9 @@ describe('League roster member mutations (HTTP)', () => {
expect(Array.isArray(members.body)).toBe(true);
const updated = (members.body as any[]).find(m => m.driverId === 'driver-2');
const updated = (members.body as { driverId: string, role: string }[]).find((m: unknown) => (m as { driverId: string }).driverId === 'driver-2');
expect(updated).toBeDefined();
expect(updated.role).toBe('steward');
expect(updated?.role).toBe('steward');
});
it('member removal is reflected in roster members read', async () => {
@@ -192,6 +193,6 @@ describe('League roster member mutations (HTTP)', () => {
.expect(200);
expect(Array.isArray(members.body)).toBe(true);
expect((members.body as any[]).some(m => m.driverId === 'driver-2')).toBe(false);
expect((members.body as never[]).some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(false);
});
});

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as never),
);
await app.init();
@@ -152,7 +152,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
const raceId: string = createRes.body.raceId;
const afterCreateRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200);
const createdRace = (afterCreateRes.body.races as any[]).find((r) => r.id === raceId);
const createdRace = (afterCreateRes.body.races as { id: string }[]).find((r) => r.id === raceId);
expect(createdRace).toMatchObject({
id: raceId,
name: 'Test Track - Test Car',
@@ -174,7 +174,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
});
const afterUpdateRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200);
const updatedRace = (afterUpdateRes.body.races as any[]).find((r) => r.id === raceId);
const updatedRace = (afterUpdateRes.body.races as { id: string }[]).find((r) => r.id === raceId);
expect(updatedRace).toMatchObject({
id: raceId,
name: 'Updated Track - Updated Car',
@@ -186,7 +186,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
});
const afterDeleteRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200);
const deletedRace = (afterDeleteRes.body.races as any[]).find((r) => r.id === raceId);
const deletedRace = (afterDeleteRes.body.races as { id: string }[]).find((r) => r.id === raceId);
expect(deletedRace).toBeUndefined();
});
});

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('League season schedule publish/unpublish (HTTP, season-scoped)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('League season schedule publish/unpublish (HTTP, season-scoped)', () =>
};
app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any),
new AuthorizationGuard(reflector, authorizationService as any),
new FeatureAvailabilityGuard(reflector, policyService as any),
new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as never),
);
await app.init();

View File

@@ -8,7 +8,7 @@ async function withUserId<T>(userId: string, fn: () => Promise<T>): Promise<T> {
const res = {};
return await new Promise<T>((resolve, reject) => {
requestContextMiddleware(req as any, res as any, () => {
requestContextMiddleware(req as never, res as never, () => {
fn().then(resolve, reject);
});
});
@@ -19,13 +19,13 @@ describe('LeagueService', () => {
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' } });
const err = async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never);
const getAllLeaguesWithCapacityUseCase: any = { execute: vi.fn(async () => Result.ok({ leagues: [] })) };
const getAllLeaguesWithCapacityAndScoringUseCase: any = { execute: vi.fn(ok) };
const getAllLeaguesWithCapacityUseCase = { execute: vi.fn(async () => Result.ok({ leagues: [] })) };
const getAllLeaguesWithCapacityAndScoringUseCase = { execute: vi.fn(ok) };
const getLeagueStandingsUseCase = { execute: vi.fn(ok) };
const getLeagueStatsUseCase = { execute: vi.fn(ok) };
const getLeagueFullConfigUseCase: any = { execute: vi.fn(ok) };
const getLeagueFullConfigUseCase = { execute: vi.fn(ok) };
const getLeagueScoringConfigUseCase = { execute: vi.fn(ok) };
const listLeagueScoringPresetsUseCase = { execute: vi.fn(ok) };
const joinLeagueUseCase = { execute: vi.fn(ok) };
@@ -120,74 +120,74 @@ describe('LeagueService', () => {
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 any)(
getAllLeaguesWithCapacityUseCase as any,
getAllLeaguesWithCapacityAndScoringUseCase as any,
getLeagueStandingsUseCase as any,
getLeagueStatsUseCase as any,
getLeagueFullConfigUseCase as any,
getLeagueScoringConfigUseCase as any,
listLeagueScoringPresetsUseCase as any,
joinLeagueUseCase as any,
transferLeagueOwnershipUseCase as any,
createLeagueWithSeasonAndScoringUseCase as any,
getTotalLeaguesUseCase as any,
getLeagueJoinRequestsUseCase as any,
approveLeagueJoinRequestUseCase as any,
rejectLeagueJoinRequestUseCase as any,
removeLeagueMemberUseCase as any,
updateLeagueMemberRoleUseCase as any,
getLeagueOwnerSummaryUseCase as any,
getLeagueProtestsUseCase as any,
getLeagueSeasonsUseCase as any,
getLeagueMembershipsUseCase as any,
getLeagueScheduleUseCase as any,
getLeagueAdminPermissionsUseCase as any,
getLeagueWalletUseCase as any,
withdrawFromLeagueWalletUseCase as any,
getSeasonSponsorshipsUseCase as any,
createLeagueSeasonScheduleRaceUseCase as any,
updateLeagueSeasonScheduleRaceUseCase as any,
deleteLeagueSeasonScheduleRaceUseCase as any,
publishLeagueSeasonScheduleUseCase as any,
unpublishLeagueSeasonScheduleUseCase as any,
logger as any,
allLeaguesWithCapacityPresenter as any,
allLeaguesWithCapacityAndScoringPresenter as any,
leagueStandingsPresenter as any,
leagueProtestsPresenter as any,
seasonSponsorshipsPresenter as any,
leagueScoringPresetsPresenter as any,
approveLeagueJoinRequestPresenter as any,
createLeaguePresenter as any,
getLeagueAdminPermissionsPresenter as any,
getLeagueMembershipsPresenter as any,
getLeagueOwnerSummaryPresenter as any,
getLeagueSeasonsPresenter as any,
joinLeaguePresenter as any,
leagueSchedulePresenter as any,
leagueStatsPresenter as any,
rejectLeagueJoinRequestPresenter as any,
removeLeagueMemberPresenter as any,
totalLeaguesPresenter as any,
transferLeagueOwnershipPresenter as any,
updateLeagueMemberRolePresenter as any,
leagueConfigPresenter as any,
leagueScoringConfigPresenter as any,
getLeagueWalletPresenter as any,
withdrawFromLeagueWalletPresenter as any,
leagueJoinRequestsPresenter as any,
createLeagueSeasonScheduleRacePresenter as any,
updateLeagueSeasonScheduleRacePresenter as any,
deleteLeagueSeasonScheduleRacePresenter as any,
publishLeagueSeasonSchedulePresenter as any,
unpublishLeagueSeasonSchedulePresenter as any,
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 any,
getLeagueRosterJoinRequestsUseCase as any,
getLeagueRosterMembersPresenter as any,
getLeagueRosterJoinRequestsPresenter as any,
getLeagueRosterMembersUseCase as never,
getLeagueRosterJoinRequestsUseCase as never,
getLeagueRosterMembersPresenter as never,
getLeagueRosterJoinRequestsPresenter as never,
);
await expect(service.getTotalLeagues()).resolves.toEqual({ total: 1 });
@@ -196,13 +196,13 @@ describe('LeagueService', () => {
});
await withUserId('user-1', async () => {
await expect(service.approveLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as any)).resolves.toEqual({
await expect(service.approveLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as never)).resolves.toEqual({
success: true,
});
});
await withUserId('user-1', async () => {
await expect(service.rejectLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as any)).resolves.toEqual({
await expect(service.rejectLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as never)).resolves.toEqual({
success: true,
});
});
@@ -212,11 +212,11 @@ describe('LeagueService', () => {
);
await withUserId('user-1', async () => {
await expect(service.getLeagueAdminPermissions({ leagueId: 'l1' } as any)).resolves.toEqual({ canManage: true });
await expect(service.getLeagueAdminPermissions({ leagueId: 'l1' } as never)).resolves.toEqual({ canManage: true });
});
await withUserId('user-1', async () => {
await expect(service.removeLeagueMember({ leagueId: 'l1', targetDriverId: 'd1' } as any)).resolves.toEqual({
await expect(service.removeLeagueMember({ leagueId: 'l1', targetDriverId: 'd1' } as never)).resolves.toEqual({
success: true,
});
});
@@ -232,7 +232,7 @@ describe('LeagueService', () => {
});
await withUserId('user-1', async () => {
await expect(service.updateLeagueMemberRole('l1', 'd1', { newRole: 'member' } as any)).resolves.toEqual({
await expect(service.updateLeagueMemberRole('l1', 'd1', { newRole: 'member' } as never)).resolves.toEqual({
success: true,
});
});
@@ -248,11 +248,11 @@ describe('LeagueService', () => {
newRole: 'member',
});
await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as any)).resolves.toEqual({ ownerId: 'o1' });
await expect(service.getLeagueProtests({ leagueId: 'l1' } as any)).resolves.toEqual({ protests: [] });
await expect(service.getLeagueSeasons({ leagueId: 'l1' } as any)).resolves.toEqual([]);
await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as never)).resolves.toEqual({ ownerId: 'o1' });
await expect(service.getLeagueProtests({ leagueId: 'l1' } as never)).resolves.toEqual({ protests: [] });
await expect(service.getLeagueSeasons({ leagueId: 'l1' } as never)).resolves.toEqual([]);
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toEqual({ form: {} });
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toEqual({ form: {} });
await expect(service.getLeagueScoringConfig('l1')).resolves.toEqual({ config: {} });
await expect(service.getLeagueMemberships('l1')).resolves.toEqual({ memberships: [] });
@@ -272,7 +272,7 @@ describe('LeagueService', () => {
// Roster admin read endpoints must be admin-gated (auth boundary) and must not execute core use cases on 403.
getLeagueAdminPermissionsUseCase.execute.mockResolvedValueOnce(
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as any,
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as never,
);
getLeagueRosterMembersUseCase.execute.mockClear();
await withUserId('user-2', async () => {
@@ -285,7 +285,7 @@ describe('LeagueService', () => {
expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
getLeagueScheduleUseCase.execute.mockClear();
await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as any)).resolves.toEqual({
await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as never)).resolves.toEqual({
seasonId: 'season-1',
published: false,
races: [],
@@ -293,7 +293,7 @@ describe('LeagueService', () => {
expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 'season-x' });
await expect(service.getLeagueStats('l1')).resolves.toEqual({ stats: {} });
await expect(service.createLeague({ name: 'n', description: 'd', ownerId: 'o' } as any)).resolves.toEqual({ id: 'l1' });
await expect(service.createLeague({ name: 'n', description: 'd', ownerId: 'o' } as never)).resolves.toEqual({ id: 'l1' });
await expect(service.listLeagueScoringPresets()).resolves.toEqual({ presets: [] });
await withUserId('user-1', async () => {
@@ -301,7 +301,7 @@ describe('LeagueService', () => {
});
await withUserId('user-1', async () => {
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as any)).resolves.toEqual({ success: true });
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as never)).resolves.toEqual({ success: true });
});
// Transfer ownership must be admin-gated and actor-derived (no payload owner/admin IDs)
@@ -310,7 +310,7 @@ describe('LeagueService', () => {
await withUserId('user-1', async () => {
await expect(
service.transferLeagueOwnership('l1', { newOwnerId: 'o2', currentOwnerId: 'spoof' } as any),
service.transferLeagueOwnership('l1', { newOwnerId: 'o2', currentOwnerId: 'spoof' } as never),
).resolves.toEqual({ success: true });
});
@@ -327,11 +327,11 @@ describe('LeagueService', () => {
// Unauthorized (non-admin/owner) actors are rejected
getLeagueAdminPermissionsUseCase.execute.mockResolvedValueOnce(
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as any,
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as never,
);
transferLeagueOwnershipUseCase.execute.mockClear();
await withUserId('user-2', async () => {
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as any)).rejects.toThrow('Forbidden');
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as never)).rejects.toThrow('Forbidden');
});
expect(transferLeagueOwnershipUseCase.execute).not.toHaveBeenCalled();
@@ -349,11 +349,11 @@ describe('LeagueService', () => {
await withUserId('user-1', async () => {
await expect(
service.publishLeagueSeasonSchedule('l1', 'season-1', {} as any),
service.publishLeagueSeasonSchedule('l1', 'season-1', {} as never),
).resolves.toEqual({ success: true, published: true });
await expect(
service.unpublishLeagueSeasonSchedule('l1', 'season-1', {} as any),
service.unpublishLeagueSeasonSchedule('l1', 'season-1', {} as never),
).resolves.toEqual({ success: true, published: false });
await expect(
@@ -361,14 +361,14 @@ describe('LeagueService', () => {
track: 'Spa',
car: 'GT3',
scheduledAtIso: new Date('2025-01-10T20:00:00Z').toISOString(),
} as any),
} as never),
).resolves.toEqual({ raceId: 'race-1' });
await expect(
service.updateLeagueSeasonScheduleRace('l1', 'season-1', 'race-1', {
track: 'Monza',
car: 'LMP2',
} as any),
} as never),
).resolves.toEqual({ success: true });
await expect(
@@ -403,7 +403,7 @@ describe('LeagueService', () => {
await withUserId('user-1', async () => {
await expect(
service.withdrawFromLeagueWallet('l1', { amount: 1, currency: 'USD', destinationAccount: 'x' } as any),
service.withdrawFromLeagueWallet('l1', { amount: 1, currency: 'USD', destinationAccount: 'x' } as never),
).resolves.toEqual({
success: true,
});
@@ -421,14 +421,14 @@ describe('LeagueService', () => {
await expect(service.getAllLeaguesWithCapacityAndScoring()).resolves.toEqual({ leagues: [], totalCount: 0 });
// Error branch: getAllLeaguesWithCapacity throws on result.isErr()
getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } }));
getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never));
await expect(service.getAllLeaguesWithCapacity()).rejects.toThrow('REPOSITORY_ERROR');
// Error branches: try/catch returning null
getLeagueFullConfigUseCase.execute.mockImplementationOnce(async () => {
throw new Error('boom');
});
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toBeNull();
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull();
getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => {
throw new Error('boom');
@@ -439,7 +439,7 @@ describe('LeagueService', () => {
getLeagueFullConfigUseCase.execute.mockImplementationOnce(async () => {
throw 'boom';
});
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toBeNull();
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull();
getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => {
throw 'boom';
@@ -447,7 +447,7 @@ describe('LeagueService', () => {
await expect(service.getLeagueScoringConfig('l1')).resolves.toBeNull();
// getLeagueAdmin error branch: fullConfigResult is Err
getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } }));
getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never));
await withUserId('user-1', async () => {
await expect(service.getLeagueAdmin('l1')).rejects.toThrow('REPOSITORY_ERROR');
});
@@ -467,4 +467,4 @@ describe('LeagueService', () => {
// keep lint happy (ensures err() used)
await err();
});
});
});

View File

@@ -168,8 +168,8 @@ describe('AllLeaguesWithCapacityAndScoringPresenter', () => {
schedule: {
startDate: new Date('2024-01-01'),
timeOfDay: { hour: 20, minute: 0 },
} as any,
} as any);
} as never,
} as never);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
@@ -264,8 +264,8 @@ describe('AllLeaguesWithCapacityAndScoringPresenter', () => {
schedule: {
startDate: new Date('2024-01-01'),
timeOfDay: { hour: 20, minute: 0 },
} as any,
} as any);
} as never,
} as never);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
@@ -464,7 +464,7 @@ describe('AllLeaguesWithCapacityAndScoringPresenter', () => {
});
// Override logoRef to uploaded type
(league as any).logoRef = MediaReference.fromJSON({
(league as unknown as { logoRef: unknown }).logoRef = MediaReference.fromJSON({
type: 'uploaded',
mediaId: 'media-123',
});

View File

@@ -28,7 +28,7 @@ describe('CreateLeaguePresenter', () => {
gameId: 'iracing',
name: 'Test League Season 1',
status: 'active',
} as any);
} as never);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
@@ -80,7 +80,7 @@ describe('CreateLeaguePresenter', () => {
gameId: 'iracing',
name: 'Another League Season 1',
status: 'active',
} as any);
} as never);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-2',
@@ -138,7 +138,7 @@ describe('CreateLeaguePresenter', () => {
gameId: 'iracing',
name: 'Test League Season 1',
status: 'active',
} as any);
} as never);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',
@@ -190,7 +190,7 @@ describe('CreateLeaguePresenter', () => {
gameId: 'iracing',
name: 'Test League Season 1',
status: 'active',
} as any);
} as never);
const scoringConfig = LeagueScoringConfig.create({
id: 'scoring-1',

View File

@@ -6,7 +6,7 @@ describe('GetLeagueMembershipsPresenter', () => {
const presenter = new GetLeagueMembershipsPresenter();
const output: GetLeagueMembershipsResult = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
league: {} as any,
league: {} as never,
memberships: [
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -15,7 +15,7 @@ describe('GetLeagueMembershipsPresenter', () => {
role: 'member',
joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
} as never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
driver: {
id: 'driver-1',
@@ -23,7 +23,7 @@ describe('GetLeagueMembershipsPresenter', () => {
name: 'John Doe',
country: 'US',
joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') },
} as any,
} as never,
},
],
};

View File

@@ -2,8 +2,8 @@
import { LeagueConfigPresenter } from './LeagueConfigPresenter';
describe('LeagueConfigPresenter', () => {
const createFullConfig = (overrides: Partial<any> = {}): any => {
const base: any = {
const createFullConfig = (overrides: Partial<any> = {}): unknown => {
const base: unknown = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
league: {
id: 'league-1',
@@ -12,7 +12,7 @@ describe('LeagueConfigPresenter', () => {
ownerId: 'owner-1',
settings: { pointsSystem: 'custom' },
createdAt: new Date(),
} as any,
} as never,
activeSeason: {
id: 'season-1',
leagueId: 'league-1',
@@ -21,9 +21,9 @@ describe('LeagueConfigPresenter', () => {
status: 'planned',
schedule: {
startDate: new Date('2025-01-05T19:00:00Z'),
timeOfDay: { hour: 20, minute: 0 } as any,
} as any,
dropPolicy: { strategy: 'bestNResults', n: 3 } as any,
timeOfDay: { hour: 20, minute: 0 } as never,
} as never,
dropPolicy: { strategy: 'bestNResults', n: 3 } as never,
stewardingConfig: {
decisionMode: 'steward_vote',
requiredVotes: 3,
@@ -34,8 +34,8 @@ describe('LeagueConfigPresenter', () => {
stewardingClosesHours: 72,
notifyAccusedOnProtest: true,
notifyOnVoteRequired: true,
} as any,
} as any,
} as never,
} as never,
scoringConfig: {
id: 'scoring-1',
seasonId: 'season-1',
@@ -43,17 +43,17 @@ describe('LeagueConfigPresenter', () => {
{
id: 'champ-1',
name: 'Drivers',
type: 'driver' as any,
sessionTypes: ['race'] as any,
type: 'driver' as never,
sessionTypes: ['race'] as never,
pointsTableBySessionType: {
race: {
getPointsForPosition: (pos: number) => (pos === 1 ? 25 : 0),
} as any,
} as never,
},
dropScorePolicy: { strategy: 'bestNResults', count: 3 } as any,
dropScorePolicy: { strategy: 'bestNResults', count: 3 } as never,
},
],
} as any,
} as never,
game: undefined,
...overrides,
};
@@ -65,7 +65,7 @@ describe('LeagueConfigPresenter', () => {
const presenter = new LeagueConfigPresenter();
const fullConfig = createFullConfig();
presenter.present({ config: fullConfig });
presenter.present({ config: fullConfig } as never);
const vm = presenter.getViewModel()!;
expect(vm).not.toBeNull();

View File

@@ -10,8 +10,28 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
}
present(result: GetLeagueFullConfigResult): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dto = result.config as any;
const dto = result.config as unknown as {
league: { id: string; name: string; description: string; settings: { pointsSystem: string } };
activeSeason?: {
stewardingConfig?: {
decisionMode: string;
requireDefense: boolean;
defenseTimeLimit: number;
voteTimeLimit: number;
protestDeadlineHours: number;
stewardingClosesHours: number;
notifyAccusedOnProtest: boolean;
notifyOnVoteRequired: boolean;
requiredVotes?: number;
};
dropPolicy?: { strategy: string; n?: number };
schedule?: {
startDate?: Date;
timeOfDay?: { hour: number; minute: number };
};
};
scoringConfig?: { championships: { sessionTypes: string[]; pointsTableBySessionType: Record<string, { getPointsForPosition: (pos: number) => number }> }[] };
};
const league = dto.league;
const settings = league.settings;
const stewarding = dto.activeSeason?.stewardingConfig;
@@ -53,7 +73,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
},
dropPolicy: {
strategy: dropPolicy?.strategy === 'none' ? 'none' : 'worst_n',
n: dropPolicy?.n,
...(dropPolicy?.n !== undefined ? { n: dropPolicy.n } : {}),
},
timings: {
raceDayOfWeek,
@@ -69,7 +89,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
stewardingClosesHours: stewarding?.stewardingClosesHours ?? 168,
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest ?? true,
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired ?? true,
requiredVotes: stewarding?.requiredVotes,
...(stewarding?.requiredVotes !== undefined ? { requiredVotes: stewarding.requiredVotes } : {}),
},
};
}

View File

@@ -13,7 +13,7 @@ describe('LeagueJoinRequestsPresenter', () => {
requestedAt: new Date('2023-01-01'),
message: 'Please accept me',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
driver: { id: 'driver-1', name: 'John Doe' } as any,
driver: { id: 'driver-1', name: 'John Doe' } as never,
},
],
};

View File

@@ -6,7 +6,7 @@ describe('LeagueOwnerSummaryPresenter', () => {
const presenter = new LeagueOwnerSummaryPresenter();
const output: GetLeagueOwnerSummaryResult = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
league: {} as any,
league: {} as never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
owner: {
id: 'driver-1',
@@ -14,9 +14,9 @@ describe('LeagueOwnerSummaryPresenter', () => {
name: 'John Doe',
country: 'US',
bio: 'Racing enthusiast',
joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') } as any,
joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') } as never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any,
} as never,
totalMembers: 50,
activeMembers: 45,
rating: 1500,

View File

@@ -19,23 +19,23 @@ describe('LeagueSchedulePresenter', () => {
seasonId: 'season-1',
published: false,
races: [{ race }],
} as any);
} as never);
const vm = presenter.getViewModel() as any;
const vm = presenter.getViewModel() as unknown as { seasonId: string, published: boolean, races: { id: string, name: string, date: string }[] };
expect(vm).not.toBeNull();
expect(vm.seasonId).toBe('season-1');
expect(vm.published).toBe(false);
expect(Array.isArray(vm.races)).toBe(true);
expect(vm.races[0]).toMatchObject({
expect(vm.races[0]!).toMatchObject({
id: 'race-1',
name: 'Spa - GT3',
date: '2025-01-02T20:00:00.000Z',
});
// Guard: dates must be ISO strings (no Date objects)
expect(typeof vm.races[0].date).toBe('string');
expect(vm.races[0].date).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
expect(typeof vm.races[0]!.date).toBe('string');
expect(vm.races[0]!.date).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
});
});