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

@@ -1,6 +1,6 @@
import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { ValidationPipe, INestApplication } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Test } from '@nestjs/testing';
import request from 'supertest';
@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Admin domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Admin domain (HTTP, module-wiring)', () => {
};
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

@@ -48,7 +48,7 @@ describe('AdminController', () => {
limit: 10,
};
const req = { user: { userId: 'admin-1' } } as any;
const req = { user: { userId: 'admin-1' } } as never;
const result = await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
@@ -81,7 +81,7 @@ describe('AdminController', () => {
sortDirection: 'desc',
};
const req = { user: { userId: 'owner-1' } } as any;
const req = { user: { userId: 'owner-1' } } as never;
const result = await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
@@ -110,7 +110,7 @@ describe('AdminController', () => {
mockService.listUsers.mockResolvedValue(mockResponse);
const query: ListUsersRequestDto = { page: 1, limit: 10 };
const req = {} as any;
const req = {} as never;
await controller.listUsers(query, req);
@@ -137,7 +137,7 @@ describe('AdminController', () => {
limit: 10,
};
const req = { user: { userId: 'admin-1' } } as any;
const req = { user: { userId: 'admin-1' } } as never;
await controller.listUsers(query, req);
expect(mockService.listUsers).toHaveBeenCalledWith({
@@ -170,7 +170,7 @@ describe('AdminController', () => {
mockService.getDashboardStats.mockResolvedValue(mockStats);
const req = { user: { userId: 'admin-1' } } as any;
const req = { user: { userId: 'admin-1' } } as never;
const result = await controller.getDashboardStats(req);
expect(mockService.getDashboardStats).toHaveBeenCalledWith({
@@ -200,7 +200,7 @@ describe('AdminController', () => {
mockService.getDashboardStats.mockResolvedValue(mockStats);
const req = {} as any;
const req = {} as never;
const result = await controller.getDashboardStats(req);
expect(mockService.getDashboardStats).toHaveBeenCalledWith({
@@ -212,7 +212,7 @@ describe('AdminController', () => {
it('should handle service errors gracefully', async () => {
mockService.getDashboardStats.mockRejectedValue(new Error('Database connection failed'));
const req = { user: { userId: 'admin-1' } } as any;
const req = { user: { userId: 'admin-1' } } as never;
await expect(controller.getDashboardStats(req)).rejects.toThrow('Database connection failed');
});

View File

@@ -19,8 +19,8 @@ describe('AdminService', () => {
beforeEach(() => {
vi.clearAllMocks();
service = new AdminService(
mockListUsersUseCase as any,
mockGetDashboardStatsUseCase as any
mockListUsersUseCase as never,
mockGetDashboardStatsUseCase as never
);
});

View File

@@ -79,8 +79,8 @@ describe('ListUsersRequestDto', () => {
it('should handle numeric string values for pagination', () => {
// Arrange & Act
const dto = new ListUsersRequestDto();
dto.page = '5' as any;
dto.limit = '25' as any;
dto.page = '5' as never;
dto.limit = '25' as never;
// Assert - Should accept the values
expect(dto.page).toBe('5');

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Analytics domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Analytics domain (HTTP, module-wiring)', () => {
};
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

@@ -27,7 +27,7 @@ describe('AnalyticsController', () => {
recordEngagement: vi.fn(),
getDashboardData: vi.fn(),
getAnalyticsMetrics: vi.fn(),
} as any;
} as never;
controller = new AnalyticsController(service);
});
@@ -121,7 +121,7 @@ describe('AnalyticsController', () => {
});
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),
@@ -168,9 +168,9 @@ describe('AnalyticsController', () => {
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

@@ -17,10 +17,10 @@ describe('AnalyticsService', () => {
};
const service = new AnalyticsService(
recordPageViewUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
recordPageViewUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
recordPageViewPresenter,
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -28,9 +28,9 @@ describe('AnalyticsService', () => {
);
const dto = await service.recordPageView({
entityType: 'league' as any,
entityType: 'league' as never,
entityId: 'l1',
visitorType: 'anonymous' as any,
visitorType: 'anonymous' as never,
sessionId: 's1',
});
@@ -40,10 +40,10 @@ describe('AnalyticsService', () => {
it('recordPageView throws on use case error', async () => {
const service = new AnalyticsService(
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -51,16 +51,16 @@ describe('AnalyticsService', () => {
);
await expect(
service.recordPageView({ entityType: 'league' as any, entityId: 'l1', visitorType: 'anonymous' as any, sessionId: 's1' }),
service.recordPageView({ entityType: 'league' as never, entityId: 'l1', visitorType: 'anonymous' as never, sessionId: 's1' }),
).rejects.toThrow('nope');
});
it('recordPageView throws with fallback message when no details.message', async () => {
const service = new AnalyticsService(
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -68,7 +68,7 @@ describe('AnalyticsService', () => {
);
await expect(
service.recordPageView({ entityType: 'league' as any, entityId: 'l1', visitorType: 'anonymous' as any, sessionId: 's1' }),
service.recordPageView({ entityType: 'league' as never, entityId: 'l1', visitorType: 'anonymous' as never, sessionId: 's1' }),
).rejects.toThrow('Failed to record page view');
});
@@ -81,10 +81,10 @@ describe('AnalyticsService', () => {
};
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
recordEngagementUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as never,
recordEngagementUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
recordEngagementPresenter,
new GetDashboardDataPresenter(),
@@ -92,8 +92,8 @@ describe('AnalyticsService', () => {
);
const dto = await service.recordEngagement({
action: 'click' as any,
entityType: 'league' as any,
action: 'click' as never,
entityType: 'league' as never,
entityId: 'l1',
actorType: 'anonymous',
sessionId: 's1',
@@ -104,10 +104,10 @@ describe('AnalyticsService', () => {
it('recordEngagement throws with details message on error', async () => {
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -116,8 +116,8 @@ describe('AnalyticsService', () => {
await expect(
service.recordEngagement({
action: 'click' as any,
entityType: 'league' as any,
action: 'click' as never,
entityType: 'league' as never,
entityId: 'l1',
actorType: 'anonymous',
sessionId: 's1',
@@ -127,10 +127,10 @@ describe('AnalyticsService', () => {
it('recordEngagement throws with fallback message when no details.message', async () => {
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -139,8 +139,8 @@ describe('AnalyticsService', () => {
await expect(
service.recordEngagement({
action: 'click' as any,
entityType: 'league' as any,
action: 'click' as never,
entityType: 'league' as never,
entityId: 'l1',
actorType: 'anonymous',
sessionId: 's1',
@@ -162,10 +162,10 @@ describe('AnalyticsService', () => {
};
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
getDashboardDataUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
getDashboardDataUseCase as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
getDashboardDataPresenter,
@@ -182,10 +182,10 @@ describe('AnalyticsService', () => {
it('getDashboardData throws with details message on error', async () => {
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -197,10 +197,10 @@ describe('AnalyticsService', () => {
it('getDashboardData throws with fallback message when no details.message', async () => {
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -224,10 +224,10 @@ describe('AnalyticsService', () => {
};
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
getAnalyticsMetricsUseCase as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
getAnalyticsMetricsUseCase as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -244,10 +244,10 @@ describe('AnalyticsService', () => {
it('getAnalyticsMetrics throws with details message on error', async () => {
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),
@@ -259,10 +259,10 @@ describe('AnalyticsService', () => {
it('getAnalyticsMetrics throws with fallback message when no details.message', async () => {
const service = new AnalyticsService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
new RecordPageViewPresenter(),
new RecordEngagementPresenter(),
new GetDashboardDataPresenter(),

View File

@@ -8,7 +8,7 @@ async function withRequestContext<T>(req: Record<string, unknown>, fn: () => Pro
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);
});
});
@@ -16,12 +16,12 @@ async function withRequestContext<T>(req: Record<string, unknown>, fn: () => Pro
describe('ActorFromSession', () => {
it('derives actor from authenticated session (request.user), not request payload', async () => {
const req: any = {
const req: unknown = {
user: { userId: 'driver-from-session' },
body: { driverId: 'driver-from-body' },
};
await withRequestContext(req, async () => {
await withRequestContext(req as Record<string, unknown>, async () => {
const actor = getActorFromRequestContext();
expect(actor).toEqual({ userId: 'driver-from-session', driverId: 'driver-from-session' });
});
@@ -32,14 +32,14 @@ describe('ActorFromSession', () => {
execute: vi.fn(async () => Result.ok(undefined)),
};
const req: any = {
const req: unknown = {
user: { userId: 'driver-from-session' },
body: { performerDriverId: 'driver-from-body' },
};
await withRequestContext(req, async () => {
await withRequestContext(req as Record<string, unknown>, async () => {
await expect(
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as any),
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as never),
).resolves.toBeUndefined();
});
@@ -52,15 +52,15 @@ describe('ActorFromSession', () => {
it('permission helper rejects when league admin check fails', async () => {
const getLeagueAdminPermissionsUseCase = {
execute: vi.fn(async () =>
Result.err({ code: 'USER_NOT_MEMBER', details: { message: 'nope' } } as any),
Result.err({ code: 'USER_NOT_MEMBER', details: { message: 'nope' } } as never),
),
};
const req: any = { user: { userId: 'driver-from-session' } };
const req: unknown = { user: { userId: 'driver-from-session' } };
await withRequestContext(req, async () => {
await withRequestContext(req as Record<string, unknown>, async () => {
await expect(
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as any),
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as never),
).rejects.toThrow('Forbidden');
});
});

View File

@@ -118,7 +118,7 @@ describe('AuthController', () => {
};
(service.getCurrentSession as Mock).mockResolvedValue(session);
const res = { json: vi.fn() } as any;
const res = { json: vi.fn() } as unknown as import('express').Response;
await controller.getSession(res);
@@ -129,7 +129,7 @@ describe('AuthController', () => {
it('should write JSON null when no session', async () => {
(service.getCurrentSession as Mock).mockResolvedValue(null);
const res = { json: vi.fn() } as any;
const res = { json: vi.fn() } as unknown as import('express').Response;
await controller.getSession(res);
@@ -151,7 +151,7 @@ describe('AuthController', () => {
});
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),
@@ -201,9 +201,9 @@ describe('AuthController', () => {
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

@@ -45,7 +45,7 @@ type SessionPort = {
};
describe('Auth guards (HTTP)', () => {
let app: any;
let app: import("@nestjs/common").INestApplication;
const sessionPort: SessionPort = {
getCurrentSession: vi.fn(async () => null),
@@ -75,9 +75,9 @@ describe('Auth guards (HTTP)', () => {
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

@@ -3,11 +3,11 @@ import { Result } from '@core/shared/application/Result';
import { AuthService } from './AuthService';
class FakeAuthSessionPresenter {
private model: any = null;
private model: unknown = null;
reset() {
this.model = null;
}
present(model: any) {
present(model: unknown) {
this.model = model;
}
get responseModel() {
@@ -17,11 +17,11 @@ class FakeAuthSessionPresenter {
}
class FakeCommandResultPresenter {
private model: any = null;
private model: unknown = null;
reset() {
this.model = null;
}
present(model: any) {
present(model: unknown) {
this.model = model;
}
get responseModel() {
@@ -33,18 +33,18 @@ class FakeCommandResultPresenter {
describe('AuthService', () => {
it('getCurrentSession returns null when no session', async () => {
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ getCurrentSession: vi.fn(async () => null), createSession: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ getCurrentSession: vi.fn(async () => null), createSession: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.getCurrentSession()).resolves.toBeNull();
@@ -52,24 +52,24 @@ describe('AuthService', () => {
it('getCurrentSession maps core session to DTO', async () => {
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{
getCurrentSession: vi.fn(async () => ({
token: 't1',
user: { id: 'u1', email: null, displayName: 'John' },
})),
createSession: vi.fn(),
} as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
} as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.getCurrentSession()).resolves.toEqual({
@@ -92,25 +92,25 @@ describe('AuthService', () => {
};
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
identitySessionPort as any,
{ execute: vi.fn() } as any,
signupUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
authSessionPresenter as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
identitySessionPort as never,
{ execute: vi.fn() } as never,
signupUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
authSessionPresenter as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
const session = await service.signupWithEmail({
email: 'e2',
password: 'p2',
displayName: 'Jane Smith',
} as any);
} as never);
expect(signupUseCase.execute).toHaveBeenCalledWith({
email: 'e2',
@@ -127,22 +127,22 @@ describe('AuthService', () => {
it('signupWithEmail throws with fallback when no details.message', async () => {
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(
service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'Jane Smith' } as any),
service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'Jane Smith' } as never),
).rejects.toThrow('Signup failed');
});
@@ -160,21 +160,21 @@ describe('AuthService', () => {
};
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
identitySessionPort as any,
loginUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
authSessionPresenter as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
identitySessionPort as never,
loginUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
authSessionPresenter as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.loginWithEmail({ email: 'e3', password: 'p3' } as any)).resolves.toEqual({
await expect(service.loginWithEmail({ email: 'e3', password: 'p3' } as never)).resolves.toEqual({
token: 't3',
user: { userId: 'u3', email: 'e3', displayName: 'Bob Wilson' },
});
@@ -192,40 +192,40 @@ describe('AuthService', () => {
it('loginWithEmail throws on use case error and prefers details.message', async () => {
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS', details: { message: 'Bad login' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS', details: { message: 'Bad login' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.loginWithEmail({ email: 'e', password: 'p' } as any)).rejects.toThrow('Bad login');
await expect(service.loginWithEmail({ email: 'e', password: 'p' } as never)).rejects.toThrow('Bad login');
});
it('loginWithEmail throws with fallback when no details.message', async () => {
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.loginWithEmail({ email: 'e', password: 'p' } as any)).rejects.toThrow('Login failed');
await expect(service.loginWithEmail({ email: 'e', password: 'p' } as never)).rejects.toThrow('Login failed');
});
it('logout returns command result on success', async () => {
@@ -237,18 +237,18 @@ describe('AuthService', () => {
};
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logoutUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
commandResultPresenter as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logoutUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
commandResultPresenter as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.logout()).resolves.toEqual({ success: true });
@@ -256,18 +256,18 @@ describe('AuthService', () => {
it('logout throws with fallback when no details.message', async () => {
const service = new AuthService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
new FakeAuthSessionPresenter() as any,
new FakeCommandResultPresenter() as any,
new FakeAuthSessionPresenter() as any,
new FakeAuthSessionPresenter() as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ getCurrentSession: vi.fn(), createSession: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
new FakeAuthSessionPresenter() as never,
new FakeCommandResultPresenter() as never,
new FakeAuthSessionPresenter() as never,
new FakeAuthSessionPresenter() as never,
);
await expect(service.logout()).rejects.toThrow('Logout failed');

View File

@@ -9,7 +9,7 @@ import { requestContextMiddleware } from '@adapters/http/RequestContext';
import { AuthModule } from './AuthModule';
describe('Auth session (HTTP, inmemory)', () => {
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({

View File

@@ -11,44 +11,44 @@ function createExecutionContext(request: Record<string, unknown>) {
describe('AuthenticationGuard', () => {
it('attaches request.user.userId from session when missing', async () => {
const request: any = {};
const request: unknown = {};
const sessionPort = {
getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })),
};
const guard = new AuthenticationGuard(sessionPort as any);
const guard = new AuthenticationGuard(sessionPort as never);
await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true);
await expect(guard.canActivate(createExecutionContext(request as Record<string, unknown>) as never)).resolves.toBe(true);
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
expect(request.user).toEqual({ userId: 'user-1' });
expect((request as { user?: unknown }).user).toEqual({ userId: 'user-1' });
});
it('does not override request.user.userId if already present', async () => {
const request: any = { user: { userId: 'already-set' } };
const request: unknown = { user: { userId: 'already-set' } };
const sessionPort = {
getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })),
};
const guard = new AuthenticationGuard(sessionPort as any);
const guard = new AuthenticationGuard(sessionPort as never);
await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true);
await expect(guard.canActivate(createExecutionContext(request as Record<string, unknown>) as never)).resolves.toBe(true);
expect(sessionPort.getCurrentSession).not.toHaveBeenCalled();
expect(request.user).toEqual({ userId: 'already-set' });
expect((request as { user?: unknown }).user).toEqual({ userId: 'already-set' });
});
it('leaves request.user undefined when no session exists', async () => {
const request: any = {};
const request: unknown = {};
const sessionPort = {
getCurrentSession: vi.fn(async () => null),
};
const guard = new AuthenticationGuard(sessionPort as any);
const guard = new AuthenticationGuard(sessionPort as never);
await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true);
await expect(guard.canActivate(createExecutionContext(request as Record<string, unknown>) as never)).resolves.toBe(true);
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
expect(request.user).toBeUndefined();
expect((request as { user?: unknown }).user).toBeUndefined();
});
});

View File

@@ -30,60 +30,60 @@ function createExecutionContext(options: { handler: Function; userId?: string })
describe('AuthorizationGuard', () => {
it('allows public routes without a user session', async () => {
const authorizationService = { getRolesForUser: vi.fn() };
const guard = new AuthorizationGuard(new Reflector(), authorizationService as any);
const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);
const ctx = createExecutionContext({
handler: DummyController.prototype.publicHandler,
});
await expect(guard.canActivate(ctx as any)).resolves.toBe(true);
await expect(guard.canActivate(ctx as never)).resolves.toBe(true);
expect(authorizationService.getRolesForUser).not.toHaveBeenCalled();
});
it('denies non-public routes by default when not authenticated', async () => {
const authorizationService = { getRolesForUser: vi.fn() };
const guard = new AuthorizationGuard(new Reflector(), authorizationService as any);
const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);
const ctx = createExecutionContext({
handler: DummyController.prototype.protectedHandler,
});
await expect(guard.canActivate(ctx as any)).rejects.toThrow(UnauthorizedException);
await expect(guard.canActivate(ctx as never)).rejects.toThrow(UnauthorizedException);
});
it('allows non-public routes when authenticated', async () => {
const authorizationService = { getRolesForUser: vi.fn().mockReturnValue([]) };
const guard = new AuthorizationGuard(new Reflector(), authorizationService as any);
const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);
const ctx = createExecutionContext({
handler: DummyController.prototype.protectedHandler,
userId: 'user-1',
});
await expect(guard.canActivate(ctx as any)).resolves.toBe(true);
await expect(guard.canActivate(ctx as never)).resolves.toBe(true);
});
it('denies routes requiring roles when user does not have any required role', async () => {
const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['user']) };
const guard = new AuthorizationGuard(new Reflector(), authorizationService as any);
const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);
const ctx = createExecutionContext({
handler: DummyController.prototype.adminHandler,
userId: 'user-1',
});
await expect(guard.canActivate(ctx as any)).rejects.toThrow(ForbiddenException);
await expect(guard.canActivate(ctx as never)).rejects.toThrow(ForbiddenException);
});
it('allows routes requiring roles when user has a required role', async () => {
const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['admin']) };
const guard = new AuthorizationGuard(new Reflector(), authorizationService as any);
const guard = new AuthorizationGuard(new Reflector(), authorizationService as never);
const ctx = createExecutionContext({
handler: DummyController.prototype.adminHandler,
userId: 'user-1',
});
await expect(guard.canActivate(ctx as any)).resolves.toBe(true);
await expect(guard.canActivate(ctx as never)).resolves.toBe(true);
});
});

View File

@@ -67,12 +67,12 @@ describe('BootstrapModule Postgres racing seed gating (unit)', () => {
const seedDemoUsersExecute = vi.fn(async () => undefined);
const bootstrapModule = new BootstrapModule(
{ execute: ensureExecute } as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ execute: ensureExecute } as never,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{
leagueRepository: { countAll: leagueCountAll },
} as any,
{ execute: seedDemoUsersExecute } as any,
} as never,
{ execute: seedDemoUsersExecute } as never,
);
await bootstrapModule.onModuleInit();

View File

@@ -77,12 +77,12 @@ describe('BootstrapModule demo user seed integration (unit)', () => {
const leagueCountAll = vi.fn(async () => leaguesCount);
const bootstrapModule = new BootstrapModule(
{ execute: ensureExecute } as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ execute: ensureExecute } as never,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{
leagueRepository: { countAll: leagueCountAll },
} as any,
{ execute: seedDemoUsersExecute } as any,
} as never,
{ execute: seedDemoUsersExecute } as never,
);
await bootstrapModule.onModuleInit();

View File

@@ -9,7 +9,7 @@ describe('Bootstrap seeding (HTTP, inmemory)', () => {
const originalEnv = { ...process.env };
let module: TestingModule | undefined;
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Dashboard domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Dashboard domain (HTTP, module-wiring)', () => {
};
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

@@ -53,7 +53,7 @@ describe('DashboardController', () => {
});
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),
@@ -108,9 +108,9 @@ describe('DashboardController', () => {
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

@@ -9,9 +9,9 @@ describe('DashboardService', () => {
const useCase = { execute: vi.fn(async () => Result.ok(mockResult)) };
const service = new DashboardService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
useCase as any,
presenter as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
useCase as never,
presenter as never,
);
await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] });
@@ -21,9 +21,9 @@ describe('DashboardService', () => {
it('getDashboardOverview throws with details message on error', async () => {
const service = new DashboardService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
{ present: vi.fn(), getResponseModel: vi.fn() } as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ present: vi.fn(), getResponseModel: vi.fn() } as never,
);
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: boom');
@@ -31,9 +31,9 @@ describe('DashboardService', () => {
it('getDashboardOverview throws with fallback message when no details.message', async () => {
const service = new DashboardService(
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ present: vi.fn(), getResponseModel: vi.fn() } as any,
{ debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ present: vi.fn(), getResponseModel: vi.fn() } as never,
);
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: Unknown error');

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Driver domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Driver domain (HTTP, module-wiring)', () => {
};
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

@@ -186,7 +186,7 @@ describe('DriverController', () => {
});
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),
@@ -225,9 +225,9 @@ describe('DriverController', () => {
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

@@ -17,6 +17,7 @@ import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
import { GetDriverLiveriesUseCase } from '@core/racing/application/use-cases/GetDriverLiveriesUseCase';
import { GetDriverUseCase } from '@core/racing/application/use-cases/GetDriverUseCase';
import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
@@ -67,6 +68,7 @@ import {
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
GET_DRIVER_USE_CASE_TOKEN,
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
@@ -276,4 +278,9 @@ export const DriverProviders: Provider[] = createLoggedProviders([
new GetDriverLiveriesUseCase(liveryRepository, logger),
inject: [LIVERY_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_DRIVER_USE_CASE_TOKEN,
useFactory: (driverRepo: IDriverRepository) => new GetDriverUseCase(driverRepo),
inject: [DRIVER_REPOSITORY_TOKEN],
},
], initLogger);

View File

@@ -14,9 +14,7 @@ describe('DriverService', () => {
const isDriverRegisteredForRaceUseCase = { execute: vi.fn() };
const updateDriverProfileUseCase = { execute: vi.fn() };
const getProfileOverviewUseCase = { execute: vi.fn() };
// Mock for repository
const driverRepository = { findById: vi.fn() };
const getDriverUseCase = { execute: vi.fn() };
// Mocks for presenters
const driversLeaderboardPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
@@ -38,8 +36,7 @@ describe('DriverService', () => {
isDriverRegisteredForRaceUseCase.execute.mockResolvedValue(Result.ok(undefined));
updateDriverProfileUseCase.execute.mockResolvedValue(Result.ok(undefined));
getProfileOverviewUseCase.execute.mockResolvedValue(Result.ok(undefined));
driverRepository.findById.mockResolvedValue(null);
getDriverUseCase.execute.mockResolvedValue(Result.ok(null));
driversLeaderboardPresenter.getResponseModel.mockReturnValue({ items: [] });
driverStatsPresenter.getResponseModel.mockReturnValue({ totalDrivers: 0 });
@@ -52,22 +49,22 @@ describe('DriverService', () => {
const createService = () => {
return new DriverService(
getDriversLeaderboardUseCase as any,
getTotalDriversUseCase as any,
getDriverLiveriesUseCase as any,
completeDriverOnboardingUseCase as any,
isDriverRegisteredForRaceUseCase as any,
updateDriverProfileUseCase as any,
getProfileOverviewUseCase as any,
driverRepository as any,
logger as any,
driversLeaderboardPresenter as any,
driverStatsPresenter as any,
completeOnboardingPresenter as any,
driverRegistrationStatusPresenter as any,
driverPresenter as any,
driverProfilePresenter as any,
getDriverLiveriesPresenter as any,
getDriversLeaderboardUseCase as never,
getTotalDriversUseCase as never,
getDriverLiveriesUseCase as never,
completeDriverOnboardingUseCase as never,
isDriverRegisteredForRaceUseCase as never,
updateDriverProfileUseCase as never,
getProfileOverviewUseCase as never,
getDriverUseCase as never,
logger as never,
driversLeaderboardPresenter as never,
driverStatsPresenter as never,
completeOnboardingPresenter as never,
driverRegistrationStatusPresenter as never,
driverPresenter as never,
driverProfilePresenter as never,
getDriverLiveriesPresenter as never,
);
};
@@ -97,7 +94,7 @@ describe('DriverService', () => {
lastName: 'L',
displayName: 'D',
country: 'DE',
} as any);
} as never);
expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({
userId: 'u1',
@@ -115,7 +112,7 @@ describe('DriverService', () => {
displayName: 'D',
country: 'DE',
bio: 'bio',
} as any);
} as never);
expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({
userId: 'u1',
@@ -132,19 +129,19 @@ describe('DriverService', () => {
driverRegistrationStatusPresenter.getResponseModel.mockReturnValue({ isRegistered: true });
await expect(
service.getDriverRegistrationStatus({ raceId: 'r1', driverId: 'd1' } as any),
service.getDriverRegistrationStatus({ raceId: 'r1', driverId: 'd1' } as never),
).resolves.toEqual({ isRegistered: true });
expect(isDriverRegisteredForRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', driverId: 'd1' });
expect(driverRegistrationStatusPresenter.getResponseModel).toHaveBeenCalled();
});
it('getCurrentDriver calls repository and returns presenter model', async () => {
it('getCurrentDriver calls use case and returns presenter model', async () => {
const service = createService();
driverRepository.findById.mockResolvedValue(null);
getDriverUseCase.execute.mockResolvedValue(Result.ok(null));
await expect(service.getCurrentDriver('u1')).resolves.toBeNull();
expect(driverRepository.findById).toHaveBeenCalledWith('u1');
expect(getDriverUseCase.execute).toHaveBeenCalledWith({ driverId: 'u1' });
expect(driverPresenter.getResponseModel).toHaveBeenCalled();
});
@@ -173,12 +170,12 @@ describe('DriverService', () => {
expect(driverPresenter.getResponseModel).toHaveBeenCalled();
});
it('getDriver calls repository and returns presenter model', async () => {
it('getDriver calls use case and returns presenter model', async () => {
const service = createService();
driverRepository.findById.mockResolvedValue(null);
getDriverUseCase.execute.mockResolvedValue(Result.ok(null));
await expect(service.getDriver('d1')).resolves.toBeNull();
expect(driverRepository.findById).toHaveBeenCalledWith('d1');
expect(getDriverUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1' });
});
it('getDriverProfile executes use case and returns presenter model', async () => {

View File

@@ -18,6 +18,7 @@ import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/Ge
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
import { UpdateDriverProfileUseCase, type UpdateDriverProfileInput } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
import { GetDriverUseCase } from '@core/racing/application/use-cases/GetDriverUseCase';
// Presenters
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';
@@ -29,11 +30,9 @@ import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter';
// Tokens
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import type { Logger } from '@core/shared/application';
import {
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
@@ -41,6 +40,7 @@ import {
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
LOGGER_TOKEN,
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
GET_DRIVER_USE_CASE_TOKEN,
} from './DriverTokens';
@Injectable()
@@ -60,8 +60,8 @@ export class DriverService {
private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
@Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN)
private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase,
@Inject(DRIVER_REPOSITORY_TOKEN)
private readonly driverRepository: IDriverRepository, // TODO must be removed from service
@Inject(GET_DRIVER_USE_CASE_TOKEN)
private readonly getDriverUseCase: GetDriverUseCase,
@Inject(LOGGER_TOKEN)
private readonly logger: Logger,
// Injected presenters (optional for module test compatibility)
@@ -138,8 +138,11 @@ export class DriverService {
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
const driver = await this.driverRepository.findById(userId);
await this.driverPresenter!.present(Result.ok(driver));
const result = await this.getDriverUseCase.execute({ driverId: userId });
if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
await this.driverPresenter!.present(Result.ok(result.unwrap()));
return this.driverPresenter!.getResponseModel();
}
@@ -157,15 +160,22 @@ export class DriverService {
await this.updateDriverProfileUseCase.execute(input);
// Get the updated driver and present it
const driver = await this.driverRepository.findById(driverId);
await this.driverPresenter!.present(Result.ok(driver));
const result = await this.getDriverUseCase.execute({ driverId });
if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
await this.driverPresenter!.present(Result.ok(result.unwrap()));
return this.driverPresenter!.getResponseModel();
}
async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`);
const driver = await this.driverRepository.findById(driverId);
const result = await this.getDriverUseCase.execute({ driverId });
if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
const driver = result.unwrap();
if (!driver) {
return null;
}
@@ -193,4 +203,4 @@ export class DriverService {
await this.getDriverLiveriesPresenter!.present(result);
return this.getDriverLiveriesPresenter!.getResponseModel()!;
}
}
}

View File

@@ -26,6 +26,7 @@ export const IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN = 'IsDriverRegisteredF
export const UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase';
export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase';
export const GET_DRIVER_LIVERIES_USE_CASE_TOKEN = 'GetDriverLiveriesUseCase';
export const GET_DRIVER_USE_CASE_TOKEN = 'GetDriverUseCase';
export const GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN = 'GetDriversLeaderboardOutputPort_TOKEN';
export const GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN = 'GetTotalDriversOutputPort_TOKEN';

View File

@@ -12,7 +12,7 @@ describe('CompleteOnboardingPresenter', () => {
const result = {
driver: {
id: 'driver-123',
} as any,
} as never,
};
presenter.present(result);
@@ -27,7 +27,7 @@ describe('CompleteOnboardingPresenter', () => {
const result = {
driver: {
id: 'driver-456',
} as any,
} as never,
};
presenter.present(result);
@@ -45,7 +45,7 @@ describe('CompleteOnboardingPresenter', () => {
});
it('should return model after present()', () => {
presenter.present({ driver: { id: 'driver-123' } } as any);
presenter.present({ driver: { id: 'driver-123' } } as never);
expect(presenter.getResponseModel()).toEqual({
success: true,
driverId: 'driver-123',

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Hello domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Hello domain (HTTP, module-wiring)', () => {
};
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

@@ -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$/);
});
});

View File

@@ -9,7 +9,7 @@ describe('Default avatar assets (HTTP)', () => {
const originalEnv = { ...process.env };
let module: TestingModule | undefined;
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();

View File

@@ -35,6 +35,8 @@ describe('MediaController', () => {
deleteMedia: ReturnType<typeof vi.fn>;
getAvatar: ReturnType<typeof vi.fn>;
updateAvatar: ReturnType<typeof vi.fn>;
getUploadedMedia: ReturnType<typeof vi.fn>;
resolveMediaReference: ReturnType<typeof vi.fn>;
};
let generationService: MediaGenerationService & {
generateDriverAvatar: ReturnType<typeof vi.fn>;
@@ -57,6 +59,8 @@ describe('MediaController', () => {
deleteMedia: vi.fn(),
getAvatar: vi.fn(),
updateAvatar: vi.fn(),
getUploadedMedia: vi.fn(),
resolveMediaReference: vi.fn(),
},
},
{
@@ -97,8 +101,8 @@ describe('MediaController', () => {
}).compile();
controller = module.get<MediaController>(MediaController);
service = module.get(MediaService) as any;
generationService = module.get(MediaGenerationService) as any;
service = module.get(MediaService) as never;
generationService = module.get(MediaGenerationService) as never;
});
const createMockResponse = (): Response => {
@@ -375,6 +379,7 @@ describe('MediaController', () => {
};
const mockService = {
getMedia: vi.fn().mockResolvedValue({ id: mediaId }),
getUploadedMedia: vi.fn().mockResolvedValue({ bytes: pngBuffer, contentType: 'image/png' }),
};
const module = await Test.createTestingModule({
@@ -409,8 +414,7 @@ describe('MediaController', () => {
await testController.getUploadedMedia(mediaId, res);
expect(mockService.getMedia).toHaveBeenCalledWith(mediaId);
expect(mockStorage.getBytes).toHaveBeenCalledWith('uploaded/media-123');
expect(mockStorage.getMetadata).toHaveBeenCalledWith('uploaded/media-123');
expect(mockService.getUploadedMedia).toHaveBeenCalledWith('uploaded/media-123');
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/png');
expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', 'public, max-age=31536000, immutable');
expect(res.status).toHaveBeenCalledWith(200);
@@ -569,7 +573,7 @@ describe('MediaController', () => {
});
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),
@@ -603,6 +607,8 @@ describe('MediaController', () => {
uploadMedia: vi.fn(),
getAvatar: vi.fn(),
updateAvatar: vi.fn(),
getUploadedMedia: vi.fn(async () => ({ bytes: Buffer.from([]), contentType: 'image/png' })),
resolveMediaReference: vi.fn(async () => '/path'),
},
},
{
@@ -657,9 +663,9 @@ describe('MediaController', () => {
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

@@ -19,9 +19,7 @@ import type { MulterFile } from './types/MulterFile';
import { MediaGenerationService } from '@core/media/domain/services/MediaGenerationService';
import type { Logger } from '@core/shared/application/Logger';
import { MediaReference } from '@core/domain/media/MediaReference';
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter';
import { LOGGER_TOKEN, MEDIA_STORAGE_PORT_TOKEN } from './MediaTokens';
import type { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
import { LOGGER_TOKEN } from './MediaTokens';
import path from 'node:path';
import fs from 'node:fs/promises';
@@ -36,8 +34,6 @@ export class MediaController {
@Inject(MediaService) private readonly mediaService: MediaService,
@Inject(MediaGenerationService) private readonly mediaGenerationService: MediaGenerationService,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
@Inject(MediaResolverAdapter) private readonly mediaResolver: MediaResolverAdapter,
@Inject(MEDIA_STORAGE_PORT_TOKEN) private readonly mediaStorage: MediaStoragePort,
) {}
@Post('avatar/generate')
@@ -272,23 +268,19 @@ export class MediaController {
// The mediaId is used as the storage key
const storageKey = `uploaded/${mediaId}`;
// Get file bytes from storage
const bytes = await this.mediaStorage.getBytes!(storageKey);
if (!bytes) {
// Get file bytes from storage via service
const result = await this.mediaService.getUploadedMedia(storageKey);
if (!result) {
this.logger.warn('[MediaController] Uploaded media file not found', { mediaId, storageKey });
res.status(HttpStatus.NOT_FOUND).json({ error: 'Media file not found' });
return;
}
// Get metadata to determine content type
const metadata = await this.mediaStorage.getMetadata!(storageKey);
const contentType = metadata?.contentType || 'application/octet-stream';
res.setHeader('Content-Type', contentType);
res.setHeader('Content-Type', result.contentType);
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
res.status(HttpStatus.OK).send(bytes);
res.status(HttpStatus.OK).send(result.bytes);
this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: bytes.length });
this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: result.bytes.length });
}
@Public()
@@ -339,7 +331,7 @@ export class MediaController {
if (ref) {
refHash = ref.hash();
resolvedPath = await this.mediaResolver.resolve(ref);
resolvedPath = await this.mediaService.resolveMediaReference(ref);
if (!resolvedPath) {
notes.push('Resolver returned null');
@@ -382,7 +374,7 @@ export class MediaController {
): Promise<void> {
this.logger.debug('[MediaController] Getting media details', { mediaId });
const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId);
if (dto) {
this.logger.info('[MediaController] Media details found', { mediaId });
res.status(HttpStatus.OK).json(dto);

View File

@@ -8,6 +8,7 @@ import { FaceValidationPort } from '@core/media/application/ports/FaceValidation
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
import type { Logger } from '@core/shared/application';
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
// Import use cases
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
@@ -16,6 +17,8 @@ import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCa
import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
import { ResolveMediaReferenceUseCase } from '@core/media/application/use-cases/ResolveMediaReferenceUseCase';
import { GetUploadedMediaUseCase } from '@core/media/application/use-cases/GetUploadedMediaUseCase';
// Import presenters
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
@@ -39,6 +42,9 @@ import {
DELETE_MEDIA_USE_CASE_TOKEN,
GET_AVATAR_USE_CASE_TOKEN,
UPDATE_AVATAR_USE_CASE_TOKEN,
RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
MEDIA_RESOLVER_PORT_TOKEN,
} from './MediaTokens';
export * from './MediaTokens';
@@ -92,7 +98,7 @@ const initLogger = InitializationLogger.getInstance();
export const MediaProviders: Provider[] = createLoggedProviders([
MediaGenerationService,
{
provide: MediaResolverAdapter,
provide: MEDIA_RESOLVER_PORT_TOKEN,
useFactory: () => new MediaResolverAdapter({}),
},
RequestAvatarGenerationPresenter,
@@ -156,4 +162,16 @@ export const MediaProviders: Provider[] = createLoggedProviders([
new UpdateAvatarUseCase(avatarRepo, logger),
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
], initLogger);
{
provide: RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
useFactory: (mediaResolver: MediaResolverPort) =>
new ResolveMediaReferenceUseCase(mediaResolver),
inject: [MEDIA_RESOLVER_PORT_TOKEN],
},
{
provide: GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
useFactory: (mediaStorage: MediaStoragePort) =>
new GetUploadedMediaUseCase(mediaStorage),
inject: [MEDIA_STORAGE_PORT_TOKEN],
},
], initLogger);

View File

@@ -16,28 +16,30 @@ describe('MediaService', () => {
};
const service = new MediaService(
requestAvatarGenerationUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
requestAvatarGenerationPresenter as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
requestAvatarGenerationUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
requestAvatarGenerationPresenter as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(
service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as any, suitColor: 'red' as any }),
service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as never, suitColor: 'red' as never }),
).resolves.toEqual({ success: true, requestId: 'r1', avatarUrls: ['u1'], errorMessage: '' });
expect(requestAvatarGenerationUseCase.execute).toHaveBeenCalledWith({
userId: 'u1',
facePhotoData: {} as any,
facePhotoData: {} as never,
suitColor: 'red',
});
expect(requestAvatarGenerationPresenter.transform).toHaveBeenCalledWith({ requestId: 'r1', status: 'completed', avatarUrls: ['u1'] });
@@ -45,23 +47,25 @@ describe('MediaService', () => {
it('requestAvatarGeneration returns failure DTO on error', async () => {
const service = new MediaService(
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'fail' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: { success: true } } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'fail' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(
service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as any, suitColor: 'red' as any }),
service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as never, suitColor: 'red' as never }),
).resolves.toEqual({
success: false,
requestId: '',
@@ -78,30 +82,32 @@ describe('MediaService', () => {
const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', url: 'https://example.com/m1.png' })) };
const service = new MediaService(
{ execute: vi.fn() } as any,
uploadMediaUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
uploadMediaPresenter as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
uploadMediaUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
uploadMediaPresenter as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(
service.uploadMedia({ file: {} as any, userId: 'u1', metadata: { a: 1 } } as any),
service.uploadMedia({ file: {} as never, userId: 'u1', metadata: { a: 1 } } as never),
).resolves.toEqual({
success: true,
mediaId: 'm1',
});
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({
file: {} as any,
file: {} as never,
uploadedBy: 'u1',
metadata: { a: 1 },
});
@@ -116,23 +122,25 @@ describe('MediaService', () => {
};
const service = new MediaService(
{ execute: vi.fn() } as any,
uploadMediaUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
uploadMediaPresenter as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
uploadMediaUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
uploadMediaPresenter as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.uploadMedia({ file: {} as any } as any)).resolves.toEqual({ success: true, mediaId: 'm1' });
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as any, uploadedBy: '', metadata: {} });
await expect(service.uploadMedia({ file: {} as never } as never)).resolves.toEqual({ success: true, mediaId: 'm1' });
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as never, uploadedBy: '', metadata: {} });
expect(uploadMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', url: 'https://example.com/m1.png' });
});
@@ -142,22 +150,24 @@ describe('MediaService', () => {
};
const service = new MediaService(
{ execute: vi.fn() } as any,
uploadMediaUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: { success: true } } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
uploadMediaUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.uploadMedia({ file: {} as any, userId: 'u1' } as any)).resolves.toEqual({
await expect(service.uploadMedia({ file: {} as never, userId: 'u1' } as never)).resolves.toEqual({
success: false,
error: 'nope',
});
@@ -172,19 +182,21 @@ describe('MediaService', () => {
};
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
getMediaUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
getMediaPresenter as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
getMediaUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
getMediaPresenter as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
const result = await service.getMedia('m1');
@@ -195,19 +207,21 @@ describe('MediaService', () => {
it('getMedia returns null on MEDIA_NOT_FOUND', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'MEDIA_NOT_FOUND', details: { message: 'n/a' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'MEDIA_NOT_FOUND', details: { message: 'n/a' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.getMedia('m1')).resolves.toBeNull();
@@ -215,19 +229,21 @@ describe('MediaService', () => {
it('getMedia throws on non-not-found error', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.getMedia('m1')).rejects.toThrow('boom');
@@ -241,19 +257,21 @@ describe('MediaService', () => {
};
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
deleteMediaUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
deleteMediaPresenter as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
deleteMediaUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
deleteMediaPresenter as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: true });
@@ -263,19 +281,21 @@ describe('MediaService', () => {
it('deleteMedia returns failure DTO on error', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: { success: true } } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: false, error: 'nope' });
@@ -290,19 +310,21 @@ describe('MediaService', () => {
};
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
getAvatarUseCase as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
getAvatarPresenter as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
getAvatarUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
getAvatarPresenter as never,
{ responseModel: {} } as never,
);
await expect(service.getAvatar('d1')).resolves.toEqual({ avatarUrl: 'https://example.com/avatar.png' });
@@ -312,19 +334,21 @@ describe('MediaService', () => {
it('getAvatar returns null on AVATAR_NOT_FOUND', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'AVATAR_NOT_FOUND', details: { message: 'n/a' } })) } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'AVATAR_NOT_FOUND', details: { message: 'n/a' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.getAvatar('d1')).resolves.toBeNull();
@@ -332,19 +356,21 @@ describe('MediaService', () => {
it('getAvatar throws on non-not-found error', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.getAvatar('d1')).rejects.toThrow('boom');
@@ -358,44 +384,48 @@ describe('MediaService', () => {
};
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
updateAvatarUseCase as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
updateAvatarPresenter as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
updateAvatarUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
updateAvatarPresenter as never,
);
await expect(service.updateAvatar('d1', { avatarUrl: 'u1' } as any)).resolves.toEqual({ success: true });
await expect(service.updateAvatar('d1', { avatarUrl: 'u1' } as never)).resolves.toEqual({ success: true });
expect(updateAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1', mediaUrl: 'u1' });
expect(updateAvatarPresenter.transform).toHaveBeenCalledWith({ avatarId: 'a1', driverId: 'd1' });
});
it('updateAvatar returns failure DTO on error', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: { success: true } } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: { success: true } } as never,
);
await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as any)).resolves.toEqual({
await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as never)).resolves.toEqual({
success: false,
error: 'nope',
});
@@ -403,23 +433,25 @@ describe('MediaService', () => {
it('requestAvatarGeneration uses fallback errorMessage when no details.message', async () => {
const service = new MediaService(
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: { success: true } } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(
service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as any, suitColor: 'red' as any }),
service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as never, suitColor: 'red' as never }),
).resolves.toEqual({
success: false,
requestId: '',
@@ -430,26 +462,28 @@ describe('MediaService', () => {
it('uploadMedia uses fallback error when no details.message', async () => {
const uploadMediaUseCase = {
execute: vi.fn(async () => Result.err({ code: 'UPLOAD_FAILED' } as any)),
execute: vi.fn(async () => Result.err({ code: 'UPLOAD_FAILED' } as never)),
};
const service = new MediaService(
{ execute: vi.fn() } as any,
uploadMediaUseCase as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: { success: true } } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
uploadMediaUseCase as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.uploadMedia({ file: {} as any, userId: 'u1' } as any)).resolves.toEqual({
await expect(service.uploadMedia({ file: {} as never, userId: 'u1' } as never)).resolves.toEqual({
success: false,
error: 'Upload failed',
});
@@ -457,19 +491,21 @@ describe('MediaService', () => {
it('getMedia throws fallback message when no details.message and not not-found', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.getMedia('m1')).rejects.toThrow('Failed to get media');
@@ -477,19 +513,21 @@ describe('MediaService', () => {
it('deleteMedia uses fallback message when no details.message', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: { success: true } } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: false, error: 'Failed to delete media' });
@@ -497,19 +535,21 @@ describe('MediaService', () => {
it('getAvatar throws fallback message when no details.message and not not-found', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
{ execute: vi.fn() } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
);
await expect(service.getAvatar('d1')).rejects.toThrow('Failed to get avatar');
@@ -517,24 +557,26 @@ describe('MediaService', () => {
it('updateAvatar uses fallback message when no details.message', async () => {
const service = new MediaService(
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn() } as any,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any,
logger as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: {} } as any,
{ responseModel: { success: true } } as any,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as never,
{ execute: vi.fn() } as never,
logger as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: { success: true } } as never,
);
await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as any)).resolves.toEqual({
await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as never)).resolves.toEqual({
success: false,
error: 'Failed to update avatar',
});
});
});
});

View File

@@ -12,6 +12,7 @@ import type { ValidateFaceInputDTO } from './dtos/ValidateFaceInputDTO';
import type { ValidateFaceOutputDTO } from './dtos/ValidateFaceOutputDTO';
import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest';
import type { MulterFile } from './types/MulterFile';
import type { MediaReference } from '@core/domain/media/MediaReference';
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type UploadMediaInput = UploadMediaInputDTO;
@@ -24,6 +25,8 @@ import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCa
import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
import { ResolveMediaReferenceUseCase } from '@core/media/application/use-cases/ResolveMediaReferenceUseCase';
import { GetUploadedMediaUseCase, type GetUploadedMediaResult } from '@core/media/application/use-cases/GetUploadedMediaUseCase';
// Presenters (now transformers)
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
@@ -40,6 +43,8 @@ import {
DELETE_MEDIA_USE_CASE_TOKEN,
GET_AVATAR_USE_CASE_TOKEN,
UPDATE_AVATAR_USE_CASE_TOKEN,
RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
LOGGER_TOKEN,
} from './MediaTokens';
import type { Logger } from '@core/shared/application';
@@ -59,6 +64,10 @@ export class MediaService {
private readonly getAvatarUseCase: GetAvatarUseCase,
@Inject(UPDATE_AVATAR_USE_CASE_TOKEN)
private readonly updateAvatarUseCase: UpdateAvatarUseCase,
@Inject(RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN)
private readonly resolveMediaReferenceUseCase: ResolveMediaReferenceUseCase,
@Inject(GET_UPLOADED_MEDIA_USE_CASE_TOKEN)
private readonly getUploadedMediaUseCase: GetUploadedMediaUseCase,
@Inject(LOGGER_TOKEN)
private readonly logger: Logger,
private readonly requestAvatarGenerationPresenter: RequestAvatarGenerationPresenter,
@@ -211,4 +220,20 @@ export class MediaService {
return { isValid: true };
}
}
async resolveMediaReference(reference: MediaReference): Promise<string | null> {
const result = await this.resolveMediaReferenceUseCase.execute({ reference });
if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
return result.unwrap();
}
async getUploadedMedia(storageKey: string): Promise<GetUploadedMediaResult | null> {
const result = await this.getUploadedMediaUseCase.execute({ storageKey });
if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
return result.unwrap();
}
}

View File

@@ -11,4 +11,7 @@ export const UPLOAD_MEDIA_USE_CASE_TOKEN = 'UploadMediaUseCase';
export const GET_MEDIA_USE_CASE_TOKEN = 'GetMediaUseCase';
export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase';
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
export const RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN = 'ResolveMediaReferenceUseCase';
export const GET_UPLOADED_MEDIA_USE_CASE_TOKEN = 'GetUploadedMediaUseCase';
export const MEDIA_RESOLVER_PORT_TOKEN = 'MediaResolverPort';

View File

@@ -3,11 +3,12 @@ import { Module } from '@nestjs/common';
import { NotificationsPersistenceModule } from '../../persistence/notifications/NotificationsPersistenceModule';
import { NotificationsController } from './NotificationsController';
import { NotificationsService } from './NotificationsService';
import { NotificationsProviders } from './NotificationsProviders';
@Module({
imports: [NotificationsPersistenceModule],
controllers: [NotificationsController],
providers: [NotificationsService],
providers: [NotificationsService, ...NotificationsProviders],
exports: [NotificationsPersistenceModule, NotificationsService],
})
export class NotificationsModule {}
export class NotificationsModule {}

View File

@@ -0,0 +1,36 @@
import { Provider } from '@nestjs/common';
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
import type { Logger } from '@core/shared/application/Logger';
import { GetUnreadNotificationsUseCase } from '@core/notifications/application/use-cases/GetUnreadNotificationsUseCase';
import { GetAllNotificationsUseCase } from '@core/notifications/application/use-cases/GetAllNotificationsUseCase';
import { MarkNotificationReadUseCase } from '@core/notifications/application/use-cases/MarkNotificationReadUseCase';
import { INotificationRepository } from '@core/notifications/domain/repositories/INotificationRepository';
import { NOTIFICATION_REPOSITORY_TOKEN } from '../../persistence/notifications/NotificationsPersistenceTokens';
import {
GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN,
GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN,
MARK_NOTIFICATION_READ_USE_CASE_TOKEN,
LOGGER_TOKEN,
} from './NotificationsTokens';
export const NotificationsProviders: Provider[] = [
{
provide: LOGGER_TOKEN,
useClass: ConsoleLogger,
},
{
provide: GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN,
useFactory: (repo: INotificationRepository, logger: Logger) => new GetUnreadNotificationsUseCase(repo, logger),
inject: [NOTIFICATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN,
useFactory: (repo: INotificationRepository, logger: Logger) => new GetAllNotificationsUseCase(repo, logger),
inject: [NOTIFICATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: MARK_NOTIFICATION_READ_USE_CASE_TOKEN,
useFactory: (repo: INotificationRepository, logger: Logger) => new MarkNotificationReadUseCase(repo, logger),
inject: [NOTIFICATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
];

View File

@@ -1,35 +1,48 @@
import { Inject, Injectable } from '@nestjs/common';
import type { INotificationRepository } from '@core/notifications/domain/repositories/INotificationRepository';
import { NOTIFICATION_REPOSITORY_TOKEN } from '../../persistence/notifications/NotificationsPersistenceTokens';
import { GetUnreadNotificationsUseCase } from '@core/notifications/application/use-cases/GetUnreadNotificationsUseCase';
import { GetAllNotificationsUseCase } from '@core/notifications/application/use-cases/GetAllNotificationsUseCase';
import { MarkNotificationReadUseCase } from '@core/notifications/application/use-cases/MarkNotificationReadUseCase';
import {
GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN,
GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN,
MARK_NOTIFICATION_READ_USE_CASE_TOKEN,
} from './NotificationsTokens';
@Injectable()
export class NotificationsService {
constructor(
@Inject(NOTIFICATION_REPOSITORY_TOKEN)
private readonly notificationRepository: INotificationRepository,
@Inject(GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN)
private readonly getUnreadNotificationsUseCase: GetUnreadNotificationsUseCase,
@Inject(GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN)
private readonly getAllNotificationsUseCase: GetAllNotificationsUseCase,
@Inject(MARK_NOTIFICATION_READ_USE_CASE_TOKEN)
private readonly markNotificationReadUseCase: MarkNotificationReadUseCase,
) {}
async getUnreadNotifications(userId: string): Promise<Array<Record<string, unknown>>> {
const notifications = await this.notificationRepository.findUnreadByRecipientId(userId);
return notifications.map(n => n.toJSON() as Record<string, unknown>);
const result = await this.getUnreadNotificationsUseCase.execute({ recipientId: userId });
if (result.isErr()) {
throw new Error(result.unwrapErr().details?.message || 'Failed to get unread notifications');
}
return result.unwrap().notifications.map(n => n.toJSON() as Record<string, unknown>);
}
async getAllNotifications(userId: string): Promise<Array<Record<string, unknown>>> {
const notifications = await this.notificationRepository.findByRecipientId(userId);
return notifications.map(n => n.toJSON() as Record<string, unknown>);
const result = await this.getAllNotificationsUseCase.execute({ recipientId: userId });
if (result.isErr()) {
throw new Error(result.unwrapErr().details?.message || 'Failed to get all notifications');
}
return result.unwrap().notifications.map(n => n.toJSON() as Record<string, unknown>);
}
async markAsRead(notificationId: string, userId: string): Promise<void> {
const notification = await this.notificationRepository.findById(notificationId);
if (!notification) {
throw new Error('Notification not found');
}
const result = await this.markNotificationReadUseCase.execute({
notificationId,
recipientId: userId,
});
if (notification.recipientId !== userId) {
throw new Error('Unauthorized');
if (result.isErr()) {
throw new Error(result.unwrapErr().details?.message || 'Failed to mark notification as read');
}
const updated = notification.markAsRead();
await this.notificationRepository.update(updated);
}
}

View File

@@ -0,0 +1,4 @@
export const GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN = Symbol('GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN');
export const GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN = Symbol('GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN');
export const MARK_NOTIFICATION_READ_USE_CASE_TOKEN = Symbol('MARK_NOTIFICATION_READ_USE_CASE_TOKEN');
export const LOGGER_TOKEN = Symbol('LOGGER_TOKEN');

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Payments domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Payments domain (HTTP, module-wiring)', () => {
};
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

@@ -377,7 +377,7 @@ describe('PaymentsController', () => {
});
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),
@@ -415,9 +415,9 @@ describe('PaymentsController', () => {
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

@@ -5,7 +5,9 @@ import { PaymentsService } from './PaymentsService';
describe('PaymentsService', () => {
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
function makeService(overrides?: Partial<Record<string, any>>) {
type MockUseCase = { execute: ReturnType<typeof vi.fn> };
function makeService(overrides?: Partial<Record<string, MockUseCase>>) {
const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) };
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) };
const updatePaymentStatusUseCase =
@@ -25,19 +27,19 @@ describe('PaymentsService', () => {
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const service = new PaymentsService(
getPaymentsUseCase as any,
createPaymentUseCase as any,
updatePaymentStatusUseCase as any,
getMembershipFeesUseCase as any,
upsertMembershipFeeUseCase as any,
updateMemberPaymentUseCase as any,
getPrizesUseCase as any,
createPrizeUseCase as any,
awardPrizeUseCase as any,
deletePrizeUseCase as any,
getWalletUseCase as any,
processWalletTransactionUseCase as any,
logger as any,
getPaymentsUseCase as never,
createPaymentUseCase as never,
updatePaymentStatusUseCase as never,
getMembershipFeesUseCase as never,
upsertMembershipFeeUseCase as never,
updateMemberPaymentUseCase as never,
getPrizesUseCase as never,
createPrizeUseCase as never,
awardPrizeUseCase as never,
deletePrizeUseCase as never,
getWalletUseCase as never,
processWalletTransactionUseCase as never,
logger as never,
);
return {
@@ -59,7 +61,7 @@ describe('PaymentsService', () => {
it('getPayments returns presenter model on success', async () => {
const { service, getPaymentsUseCase } = makeService();
await expect(service.getPayments({ leagueId: 'l1' } as any)).resolves.toEqual({ payments: [] });
await expect(service.getPayments({ leagueId: 'l1' } as never)).resolves.toEqual({ payments: [] });
expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
@@ -67,12 +69,12 @@ describe('PaymentsService', () => {
const { service } = makeService({
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
});
await expect(service.getPayments({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
});
it('createPayment returns presenter model on success', async () => {
const { service, createPaymentUseCase } = makeService();
await expect(service.createPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ paymentId: 'p1' });
await expect(service.createPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ paymentId: 'p1' });
expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
@@ -80,12 +82,12 @@ describe('PaymentsService', () => {
const { service } = makeService({
createPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
});
await expect(service.createPayment({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
});
it('updatePaymentStatus returns presenter model on success', async () => {
const { service, updatePaymentStatusUseCase } = makeService();
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).resolves.toEqual({ success: true });
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).resolves.toEqual({ success: true });
expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
});
@@ -93,7 +95,7 @@ describe('PaymentsService', () => {
const { service } = makeService({
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
});
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
});
it('getMembershipFees returns viewModel on success', async () => {
@@ -101,7 +103,7 @@ describe('PaymentsService', () => {
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) }
});
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).resolves.toEqual({
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).resolves.toEqual({
fee: { amount: 1 },
payments: [],
});
@@ -112,22 +114,22 @@ describe('PaymentsService', () => {
const { service } = makeService({
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
});
await expect(service.getMembershipFees({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
await expect(service.getMembershipFees({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
});
it('upsertMembershipFee returns viewModel on success', async () => {
const { service, upsertMembershipFeeUseCase } = makeService();
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
it('upsertMembershipFee throws on error branch (defensive check)', async () => {
const { service } = makeService({
upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) },
upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) },
});
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).rejects.toThrow(
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).rejects.toThrow(
'Failed to upsert membership fee',
);
});
@@ -135,7 +137,7 @@ describe('PaymentsService', () => {
it('updateMemberPayment returns viewModel on success', async () => {
const { service, updateMemberPaymentUseCase } = makeService();
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
@@ -144,7 +146,7 @@ describe('PaymentsService', () => {
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
});
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
});
it('getPrizes maps seasonId optional', async () => {
@@ -153,10 +155,10 @@ describe('PaymentsService', () => {
getPrizesUseCase,
});
await expect(service.getPrizes({ leagueId: 'l1' } as any)).resolves.toEqual({ prizes: [] });
await expect(service.getPrizes({ leagueId: 'l1' } as never)).resolves.toEqual({ prizes: [] });
expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as any)).resolves.toEqual({ prizes: [] });
await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as never)).resolves.toEqual({ prizes: [] });
expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 's1' });
});
@@ -166,7 +168,7 @@ describe('PaymentsService', () => {
createPrizeUseCase,
});
await expect(service.createPrize({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
await expect(service.createPrize({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
expect(createPrizeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
@@ -176,7 +178,7 @@ describe('PaymentsService', () => {
awardPrizeUseCase,
});
await expect(service.awardPrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
await expect(service.awardPrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true });
expect(awardPrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
});
@@ -186,7 +188,7 @@ describe('PaymentsService', () => {
deletePrizeUseCase,
});
await expect(service.deletePrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
await expect(service.deletePrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true });
expect(deletePrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
});
@@ -196,7 +198,7 @@ describe('PaymentsService', () => {
getWalletUseCase,
});
await expect(service.getWallet({ leagueId: 'l1' } as any)).resolves.toEqual({ balance: 10 });
await expect(service.getWallet({ leagueId: 'l1' } as never)).resolves.toEqual({ balance: 10 });
expect(getWalletUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
@@ -206,47 +208,47 @@ describe('PaymentsService', () => {
processWalletTransactionUseCase,
});
await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
await expect(service.processWalletTransaction({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
expect(processWalletTransactionUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
});
it('getPayments throws fallback message when error has no code', async () => {
const { service } = makeService({
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
});
await expect(service.getPayments({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to get payments');
await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to get payments');
});
it('createPayment throws fallback message when error has no code', async () => {
const { service } = makeService({
createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
});
await expect(service.createPayment({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to create payment');
await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to create payment');
});
it('updatePaymentStatus throws fallback message when error has no code', async () => {
const { service } = makeService({
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
});
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).rejects.toThrow('Failed to update payment status');
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('Failed to update payment status');
});
it('getMembershipFees throws fallback message when error has no code', async () => {
const { service } = makeService({
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
});
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).rejects.toThrow('Failed to get membership fees');
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).rejects.toThrow('Failed to get membership fees');
});
it('updateMemberPayment throws fallback message when error has no code', async () => {
const { service } = makeService({
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
});
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to update member payment');
await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to update member payment');
});
});

View File

@@ -1,12 +0,0 @@
export * from './GetPaymentsPresenter';
export * from './CreatePaymentPresenter';
export * from './UpdatePaymentStatusPresenter';
export * from './GetMembershipFeesPresenter';
export * from './UpsertMembershipFeePresenter';
export * from './UpdateMemberPaymentPresenter';
export * from './GetPrizesPresenter';
export * from './CreatePrizePresenter';
export * from './AwardPrizePresenter';
export * from './DeletePrizePresenter';
export * from './GetWalletPresenter';
export * from './ProcessWalletTransactionPresenter';

View File

@@ -14,7 +14,7 @@ import { FeatureAvailabilityGuard } from './FeatureAvailabilityGuard';
describe('Policy domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -61,9 +61,9 @@ describe('Policy domain (HTTP, module-wiring)', () => {
};
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('Protests domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Protests domain (HTTP, module-wiring)', () => {
};
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('Race domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Race domain (HTTP, module-wiring)', () => {
};
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

@@ -58,8 +58,8 @@ describe('RaceController', () => {
describe('getAllRaces', () => {
it('should return all races view model', async () => {
const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as any;
service.getAllRaces.mockResolvedValue(mockPresenter);
const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as unknown as { viewModel: unknown };
service.getAllRaces.mockResolvedValue(mockPresenter as never);
const result = await controller.getAllRaces();
@@ -70,8 +70,8 @@ describe('RaceController', () => {
describe('getTotalRaces', () => {
it('should return total races count view model', async () => {
const mockPresenter = { viewModel: { totalRaces: 5 } } as any;
service.getTotalRaces.mockResolvedValue(mockPresenter);
const mockPresenter = { viewModel: { totalRaces: 5 } } as unknown as { viewModel: unknown };
service.getTotalRaces.mockResolvedValue(mockPresenter as never);
const result = await controller.getTotalRaces();
@@ -81,7 +81,7 @@ describe('RaceController', () => {
});
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),
@@ -120,9 +120,9 @@ describe('RaceController', () => {
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

@@ -1,11 +1,11 @@
import { describe, expect, it, vi } from 'vitest';
import { describe, expect, it, vi, type Mock } from 'vitest';
import { RaceService } from './RaceService';
import { Result } from '@core/shared/application/Result';
describe('RaceService', () => {
it('invokes each use case and returns the corresponding presenter', async () => {
// Mock use cases to return Result.ok()
const mkUseCase = (resultValue: any = { success: true }) => ({
const mkUseCase = (resultValue: unknown = { success: true }) => ({
execute: vi.fn(async () => Result.ok(resultValue))
});
@@ -32,51 +32,51 @@ describe('RaceService', () => {
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
const getAllRacesPresenter = { present: vi.fn() } as any;
const getTotalRacesPresenter = { present: vi.fn() } as any;
const importRaceResultsApiPresenter = { present: vi.fn() } as any;
const raceDetailPresenter = { present: vi.fn() } as any;
const racesPageDataPresenter = { present: vi.fn() } as any;
const allRacesPageDataPresenter = { present: vi.fn() } as any;
const raceResultsDetailPresenter = { present: vi.fn() } as any;
const raceWithSOFPresenter = { present: vi.fn() } as any;
const raceProtestsPresenter = { present: vi.fn() } as any;
const racePenaltiesPresenter = { present: vi.fn() } as any;
const commandResultPresenter = { present: vi.fn() } as any;
const getAllRacesPresenter = { present: vi.fn() } as unknown as { present: Mock };
const getTotalRacesPresenter = { present: vi.fn() } as unknown as { present: Mock };
const importRaceResultsApiPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceDetailPresenter = { present: vi.fn() } as unknown as { present: Mock };
const racesPageDataPresenter = { present: vi.fn() } as unknown as { present: Mock };
const allRacesPageDataPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceResultsDetailPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceWithSOFPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceProtestsPresenter = { present: vi.fn() } as unknown as { present: Mock };
const racePenaltiesPresenter = { present: vi.fn() } as unknown as { present: Mock };
const commandResultPresenter = { present: vi.fn() } as unknown as { present: Mock };
const service = new RaceService(
getAllRacesUseCase as any,
getTotalRacesUseCase as any,
importRaceResultsApiUseCase as any,
getRaceDetailUseCase as any,
getRacesPageDataUseCase as any,
getAllRacesPageDataUseCase as any,
getRaceResultsDetailUseCase as any,
getRaceWithSOFUseCase as any,
getRaceProtestsUseCase as any,
getRacePenaltiesUseCase as any,
registerForRaceUseCase as any,
withdrawFromRaceUseCase as any,
cancelRaceUseCase as any,
completeRaceUseCase as any,
fileProtestUseCase as any,
quickPenaltyUseCase as any,
applyPenaltyUseCase as any,
requestProtestDefenseUseCase as any,
reviewProtestUseCase as any,
reopenRaceUseCase as any,
logger as any,
getAllRacesPresenter,
getTotalRacesPresenter,
importRaceResultsApiPresenter,
raceDetailPresenter,
racesPageDataPresenter,
allRacesPageDataPresenter,
raceResultsDetailPresenter,
raceWithSOFPresenter,
raceProtestsPresenter,
racePenaltiesPresenter,
commandResultPresenter,
getAllRacesUseCase as never,
getTotalRacesUseCase as never,
importRaceResultsApiUseCase as never,
getRaceDetailUseCase as never,
getRacesPageDataUseCase as never,
getAllRacesPageDataUseCase as never,
getRaceResultsDetailUseCase as never,
getRaceWithSOFUseCase as never,
getRaceProtestsUseCase as never,
getRacePenaltiesUseCase as never,
registerForRaceUseCase as never,
withdrawFromRaceUseCase as never,
cancelRaceUseCase as never,
completeRaceUseCase as never,
fileProtestUseCase as never,
quickPenaltyUseCase as never,
applyPenaltyUseCase as never,
requestProtestDefenseUseCase as never,
reviewProtestUseCase as never,
reopenRaceUseCase as never,
logger as never,
getAllRacesPresenter as never,
getTotalRacesPresenter as never,
importRaceResultsApiPresenter as never,
raceDetailPresenter as never,
racesPageDataPresenter as never,
allRacesPageDataPresenter as never,
raceResultsDetailPresenter as never,
raceWithSOFPresenter as never,
raceProtestsPresenter as never,
racePenaltiesPresenter as never,
commandResultPresenter as never,
);
expect(await service.getAllRaces()).toBe(getAllRacesPresenter);
@@ -87,11 +87,11 @@ describe('RaceService', () => {
expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({});
expect(getTotalRacesPresenter.present).toHaveBeenCalledWith({ totalRaces: 0 });
expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as any)).toBe(importRaceResultsApiPresenter);
expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as never)).toBe(importRaceResultsApiPresenter);
expect(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' });
expect(importRaceResultsApiPresenter.present).toHaveBeenCalledWith({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] });
expect(await service.getRaceDetail({ raceId: 'r1' } as any)).toBe(raceDetailPresenter);
expect(await service.getRaceDetail({ raceId: 'r1' } as never)).toBe(raceDetailPresenter);
expect(getRaceDetailUseCase.execute).toHaveBeenCalled();
expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter);
@@ -118,43 +118,43 @@ describe('RaceService', () => {
expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(racePenaltiesPresenter.present).toHaveBeenCalledWith({ penalties: [], drivers: [] });
expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as never)).toBe(commandResultPresenter);
expect(registerForRaceUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter);
expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as never)).toBe(commandResultPresenter);
expect(withdrawFromRaceUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.cancelRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
expect(await service.cancelRace({ raceId: 'r1' } as never, 'admin')).toBe(commandResultPresenter);
expect(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' });
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.completeRace({ raceId: 'r1' } as any)).toBe(commandResultPresenter);
expect(await service.completeRace({ raceId: 'r1' } as never)).toBe(commandResultPresenter);
expect(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.reopenRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter);
expect(await service.reopenRace({ raceId: 'r1' } as never, 'admin')).toBe(commandResultPresenter);
expect(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' });
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.fileProtest({} as any)).toBe(commandResultPresenter);
expect(await service.fileProtest({} as never)).toBe(commandResultPresenter);
expect(fileProtestUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.applyQuickPenalty({} as any)).toBe(commandResultPresenter);
expect(await service.applyQuickPenalty({} as never)).toBe(commandResultPresenter);
expect(quickPenaltyUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.applyPenalty({} as any)).toBe(commandResultPresenter);
expect(await service.applyPenalty({} as never)).toBe(commandResultPresenter);
expect(applyPenaltyUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.requestProtestDefense({} as any)).toBe(commandResultPresenter);
expect(await service.requestProtestDefense({} as never)).toBe(commandResultPresenter);
expect(requestProtestDefenseUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
expect(await service.reviewProtest({} as any)).toBe(commandResultPresenter);
expect(await service.reviewProtest({} as never)).toBe(commandResultPresenter);
expect(reviewProtestUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled();
});

View File

@@ -35,13 +35,13 @@ describe('GetAllRacesPresenter', () => {
strengthOfField: 1800,
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any,
] as never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
leagues: [
{ id: 'league-1', name: 'League One' },
{ id: 'league-2', name: 'League Two' },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any,
] as never,
totalCount: 3,
};
@@ -66,9 +66,9 @@ describe('GetAllRacesPresenter', () => {
const output: GetAllRacesResult = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
races: [] as any,
races: [] as never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
leagues: [] as any,
leagues: [] as never,
totalCount: 0,
};

View File

@@ -23,12 +23,12 @@ describe('RacePenaltiesPresenter', () => {
const mockDriver = {
id: 'driver-456',
name: { toString: () => 'John Doe' } as any,
name: { toString: () => 'John Doe' } as never,
};
const result: GetRacePenaltiesResult = {
penalties: [mockPenalty as any],
drivers: [mockDriver as any],
penalties: [mockPenalty as never],
drivers: [mockDriver as never],
};
presenter.present(result);
@@ -63,7 +63,7 @@ describe('RacePenaltiesPresenter', () => {
reason: 'Reason 1',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
} as never,
{
id: 'penalty-2',
driverId: 'driver-2',
@@ -72,11 +72,11 @@ describe('RacePenaltiesPresenter', () => {
reason: 'Reason 2',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:05:00Z'),
} as any,
} as never,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as never,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as never,
],
};
@@ -114,9 +114,9 @@ describe('RacePenaltiesPresenter', () => {
reason: 'Reason',
issuedBy: 'steward-1',
issuedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
} as never,
],
drivers: [{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any],
drivers: [{ id: 'driver-1', name: { toString: () => 'Driver One' } } as never],
};
presenter.present(result);
@@ -128,8 +128,8 @@ describe('RacePenaltiesPresenter', () => {
describe('reset', () => {
it('should clear the model', () => {
presenter.present({
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any],
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as never],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never],
});
expect(presenter.responseModel).toBeDefined();
@@ -147,8 +147,8 @@ describe('RacePenaltiesPresenter', () => {
describe('viewModel', () => {
it('should return same as responseModel', () => {
const result: GetRacePenaltiesResult = {
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any],
penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as never],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never],
};
presenter.present(result);

View File

@@ -15,26 +15,26 @@ describe('RaceProtestsPresenter', () => {
protestingDriverId: 'driver-456',
accusedDriverId: 'driver-789',
incident: {
lap: { toNumber: () => 5 } as any,
description: { toString: () => 'Contact at turn 3' } as any,
lap: { toNumber: () => 5 } as never,
description: { toString: () => 'Contact at turn 3' } as never,
},
status: { toString: () => 'pending' } as any,
status: { toString: () => 'pending' } as never,
filedAt: new Date('2024-01-01T10:30:00Z'),
};
const mockDriver1 = {
id: 'driver-456',
name: { toString: () => 'John Doe' } as any,
name: { toString: () => 'John Doe' } as never,
};
const mockDriver2 = {
id: 'driver-789',
name: { toString: () => 'Jane Smith' } as any,
name: { toString: () => 'Jane Smith' } as never,
};
const result: GetRaceProtestsResult = {
protests: [mockProtest as any],
drivers: [mockDriver1 as any, mockDriver2 as any],
protests: [mockProtest as never],
drivers: [mockDriver1 as never, mockDriver2 as never],
};
presenter.present(result);
@@ -68,28 +68,28 @@ describe('RaceProtestsPresenter', () => {
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: { toNumber: () => 3 } as any,
description: { toString: () => 'Incident 1' } as any
lap: { toNumber: () => 3 } as never,
description: { toString: () => 'Incident 1' } as never
},
status: { toString: () => 'pending' } as any,
status: { toString: () => 'pending' } as never,
filedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
} as never,
{
id: 'protest-2',
protestingDriverId: 'driver-3',
accusedDriverId: 'driver-1',
incident: {
lap: { toNumber: () => 7 } as any,
description: { toString: () => 'Incident 2' } as any
lap: { toNumber: () => 7 } as never,
description: { toString: () => 'Incident 2' } as never
},
status: { toString: () => 'reviewed' } as any,
status: { toString: () => 'reviewed' } as never,
filedAt: new Date('2024-01-01T10:10:00Z'),
} as any,
} as never,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
{ id: 'driver-3', name: { toString: () => 'Driver Three' } } as any,
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as never,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as never,
{ id: 'driver-3', name: { toString: () => 'Driver Three' } } as never,
],
};
@@ -125,16 +125,16 @@ describe('RaceProtestsPresenter', () => {
protestingDriverId: 'driver-1',
accusedDriverId: 'driver-2',
incident: {
lap: { toNumber: () => 5 } as any,
description: { toString: () => 'Test' } as any
lap: { toNumber: () => 5 } as never,
description: { toString: () => 'Test' } as never
},
status: { toString: () => 'approved' } as any,
status: { toString: () => 'approved' } as never,
filedAt: new Date('2024-01-01T10:00:00Z'),
} as any,
} as never,
],
drivers: [
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as any,
{ id: 'driver-1', name: { toString: () => 'Driver One' } } as never,
{ id: 'driver-2', name: { toString: () => 'Driver Two' } } as never,
],
};
@@ -152,13 +152,13 @@ describe('RaceProtestsPresenter', () => {
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: {
lap: { toNumber: () => 1 } as any,
description: { toString: () => 'test' } as any
lap: { toNumber: () => 1 } as never,
description: { toString: () => 'test' } as never
},
status: { toString: () => 'pending' } as any,
status: { toString: () => 'pending' } as never,
filedAt: new Date()
} as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any],
} as never],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never, { id: 'd2', name: { toString: () => 'D2' } } as never],
});
expect(presenter.responseModel).toBeDefined();
@@ -181,13 +181,13 @@ describe('RaceProtestsPresenter', () => {
protestingDriverId: 'd1',
accusedDriverId: 'd2',
incident: {
lap: { toNumber: () => 1 } as any,
description: { toString: () => 'test' } as any
lap: { toNumber: () => 1 } as never,
description: { toString: () => 'test' } as never
},
status: { toString: () => 'pending' } as any,
status: { toString: () => 'pending' } as never,
filedAt: new Date()
} as any],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any],
} as never],
drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never, { id: 'd2', name: { toString: () => 'D2' } } as never],
};
presenter.present(result);

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Sponsor domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Sponsor domain (HTTP, module-wiring)', () => {
};
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

@@ -53,7 +53,7 @@ describe('SponsorController', () => {
describe('getEntitySponsorshipPricing', () => {
it('should return sponsorship pricing', async () => {
const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] };
sponsorService.getEntitySponsorshipPricing.mockResolvedValue(mockResult as any);
sponsorService.getEntitySponsorshipPricing.mockResolvedValue(mockResult as never);
const result = await controller.getEntitySponsorshipPricing();
@@ -65,7 +65,7 @@ describe('SponsorController', () => {
describe('getSponsors', () => {
it('should return sponsors list', async () => {
const mockResult = { sponsors: [] };
sponsorService.getSponsors.mockResolvedValue(mockResult as any);
sponsorService.getSponsors.mockResolvedValue(mockResult as never);
const result = await controller.getSponsors();
@@ -78,9 +78,9 @@ describe('SponsorController', () => {
it('should create sponsor', async () => {
const input = { name: 'Test Sponsor', contactEmail: 'test@example.com' };
const mockResult = { sponsor: { id: 's1', name: 'Test Sponsor' } };
sponsorService.createSponsor.mockResolvedValue(mockResult as any);
sponsorService.createSponsor.mockResolvedValue(mockResult as never);
const result = await controller.createSponsor(input as any);
const result = await controller.createSponsor(input as never);
expect(result).toEqual(mockResult);
expect(sponsorService.createSponsor).toHaveBeenCalledWith(input);
@@ -90,8 +90,8 @@ describe('SponsorController', () => {
describe('getSponsorDashboard', () => {
it('should return sponsor dashboard', async () => {
const sponsorId = 's1';
const mockResult = { sponsorId, metrics: {} as any, sponsoredLeagues: [], investment: {} as any };
sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as any);
const mockResult = { sponsorId, metrics: {} as never, sponsoredLeagues: [], investment: {} as never };
sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as never);
const result = await controller.getSponsorDashboard(sponsorId);
@@ -122,7 +122,7 @@ describe('SponsorController', () => {
currency: 'USD',
},
};
sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as any);
sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as never);
const result = await controller.getSponsorSponsorships(sponsorId);
@@ -142,7 +142,7 @@ describe('SponsorController', () => {
it('should return sponsor', async () => {
const sponsorId = 's1';
const mockResult = { sponsor: { id: sponsorId, name: 'S1' } };
sponsorService.getSponsor.mockResolvedValue(mockResult as any);
sponsorService.getSponsor.mockResolvedValue(mockResult as never);
const result = await controller.getSponsor(sponsorId);
@@ -167,7 +167,7 @@ describe('SponsorController', () => {
requests: [],
totalCount: 0,
};
sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as any);
sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as never);
const result = await controller.getPendingSponsorshipRequests(query);
@@ -188,9 +188,9 @@ describe('SponsorController', () => {
platformFee: 10,
netAmount: 90,
};
sponsorService.acceptSponsorshipRequest.mockResolvedValue(mockResult as any);
sponsorService.acceptSponsorshipRequest.mockResolvedValue(mockResult as never);
const result = await controller.acceptSponsorshipRequest(requestId, input as any);
const result = await controller.acceptSponsorshipRequest(requestId, input as never);
expect(result).toEqual(mockResult);
expect(sponsorService.acceptSponsorshipRequest).toHaveBeenCalledWith(
@@ -204,7 +204,7 @@ describe('SponsorController', () => {
const input = { respondedBy: 'u1' };
sponsorService.acceptSponsorshipRequest.mockRejectedValue(new Error('Accept sponsorship request failed'));
await expect(controller.acceptSponsorshipRequest(requestId, input as any)).rejects.toBeInstanceOf(Error);
await expect(controller.acceptSponsorshipRequest(requestId, input as never)).rejects.toBeInstanceOf(Error);
});
});
@@ -218,9 +218,9 @@ describe('SponsorController', () => {
rejectedAt: new Date(),
reason: 'Not interested',
};
sponsorService.rejectSponsorshipRequest.mockResolvedValue(mockResult as any);
sponsorService.rejectSponsorshipRequest.mockResolvedValue(mockResult as never);
const result = await controller.rejectSponsorshipRequest(requestId, input as any);
const result = await controller.rejectSponsorshipRequest(requestId, input as never);
expect(result).toEqual(mockResult);
expect(sponsorService.rejectSponsorshipRequest).toHaveBeenCalledWith(
@@ -235,7 +235,7 @@ describe('SponsorController', () => {
const input = { respondedBy: 'u1' };
sponsorService.rejectSponsorshipRequest.mockRejectedValue(new Error('Reject sponsorship request failed'));
await expect(controller.rejectSponsorshipRequest(requestId, input as any)).rejects.toBeInstanceOf(Error);
await expect(controller.rejectSponsorshipRequest(requestId, input as never)).rejects.toBeInstanceOf(Error);
});
});
@@ -254,7 +254,7 @@ describe('SponsorController', () => {
averageMonthlySpend: 0,
},
};
sponsorService.getSponsorBilling.mockResolvedValue(mockResult as any);
sponsorService.getSponsorBilling.mockResolvedValue(mockResult as never);
const result = await controller.getSponsorBilling(sponsorId);
@@ -265,8 +265,8 @@ describe('SponsorController', () => {
describe('getAvailableLeagues', () => {
it('should return available leagues', async () => {
const mockResult: any[] = [];
sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as any);
const mockResult: unknown[] = [];
sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.getAvailableLeagues();
@@ -279,11 +279,11 @@ describe('SponsorController', () => {
it('should return league detail', async () => {
const leagueId = 'league-1';
const mockResult = {
league: { id: leagueId } as any,
league: { id: leagueId } as never,
drivers: [],
races: [],
};
sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as any);
sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.getLeagueDetail(leagueId);
@@ -296,11 +296,11 @@ describe('SponsorController', () => {
it('should return sponsor settings', async () => {
const sponsorId = 's1';
const mockResult = {
profile: {} as any,
notifications: {} as any,
privacy: {} as any,
profile: {} as never,
notifications: {} as never,
privacy: {} as never,
};
sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any);
sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.getSponsorSettings(sponsorId);
@@ -314,7 +314,7 @@ describe('SponsorController', () => {
const sponsorId = 's1';
const input = {};
const mockResult = { success: true };
sponsorService.updateSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any);
sponsorService.updateSponsorSettings.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.updateSponsorSettings(sponsorId, input);

View File

@@ -167,14 +167,14 @@ describe('SponsorService', () => {
it('throws using error.message when details.message is missing', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR', message: 'Boom' } as any));
createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR', message: 'Boom' } as never));
await expect(service.createSponsor(input)).rejects.toThrow('Boom');
});
it('throws default message when details.message and message are missing', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' };
createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR' } as any));
createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR' } as never));
await expect(service.createSponsor(input)).rejects.toThrow('Failed to create sponsor');
});
@@ -476,7 +476,7 @@ describe('SponsorService', () => {
});
it('throws on error', async () => {
getSponsorBillingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' } as any));
getSponsorBillingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' } as never));
await expect(service.getSponsorBilling('s1')).rejects.toThrow('Sponsor billing not found');
});
});

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('Team domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env };
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeAll(async () => {
vi.resetModules();
@@ -62,9 +62,9 @@ describe('Team domain (HTTP, module-wiring)', () => {
};
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

@@ -61,7 +61,7 @@ describe('TeamController', () => {
const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false };
service.getDetails.mockResolvedValue(result);
const mockReq = { user: { userId } } as any;
const mockReq = { user: { userId } } as never;
const response = await controller.getDetails(teamId, mockReq);
@@ -103,7 +103,7 @@ describe('TeamController', () => {
const result = { id: 'team-456', success: true };
service.create.mockResolvedValue(result);
const mockReq = { user: { userId } } as any;
const mockReq = { user: { userId } } as never;
const response = await controller.create(input, mockReq);
@@ -120,7 +120,7 @@ describe('TeamController', () => {
const result = { success: true };
service.update.mockResolvedValue(result);
const mockReq = { user: { userId } } as any;
const mockReq = { user: { userId } } as never;
const response = await controller.update(teamId, input, mockReq);
@@ -157,7 +157,7 @@ describe('TeamController', () => {
});
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),
@@ -196,9 +196,9 @@ describe('TeamController', () => {
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

@@ -1,6 +1,24 @@
import { Provider } from '@nestjs/common';
import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN, MEDIA_REPOSITORY_TOKEN, MEDIA_RESOLVER_TOKEN } from './TeamTokens';
import {
IMAGE_SERVICE_TOKEN,
LOGGER_TOKEN,
MEDIA_REPOSITORY_TOKEN,
MEDIA_RESOLVER_TOKEN,
TEAM_REPOSITORY_TOKEN,
TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
DRIVER_REPOSITORY_TOKEN,
TEAM_STATS_REPOSITORY_TOKEN,
GET_ALL_TEAMS_USE_CASE_TOKEN,
GET_TEAM_DETAILS_USE_CASE_TOKEN,
GET_TEAM_MEMBERS_USE_CASE_TOKEN,
GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
CREATE_TEAM_USE_CASE_TOKEN,
UPDATE_TEAM_USE_CASE_TOKEN,
GET_DRIVER_TEAM_USE_CASE_TOKEN,
GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
JOIN_TEAM_USE_CASE_TOKEN,
} from './TeamTokens';
export {
TEAM_REPOSITORY_TOKEN,
@@ -15,6 +33,10 @@ export {
// Import core interfaces
import type { Logger } from '@core/shared/application/Logger';
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
// Import concrete implementations
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
@@ -22,6 +44,17 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter';
// Import use cases
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
// Import presenters
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
@@ -69,4 +102,59 @@ export const TeamProviders: Provider[] = [
},
inject: [MEDIA_RESOLVER_TOKEN],
},
];
// Use Cases
{
provide: GET_ALL_TEAMS_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, statsRepo: ITeamStatsRepository, logger: Logger) =>
new GetAllTeamsUseCase(teamRepo, membershipRepo, statsRepo, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_TEAM_DETAILS_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
new GetTeamDetailsUseCase(teamRepo, membershipRepo),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: GET_TEAM_MEMBERS_USE_CASE_TOKEN,
useFactory: (membershipRepo: ITeamMembershipRepository, driverRepo: IDriverRepository, teamRepo: ITeamRepository, logger: Logger) =>
new GetTeamMembersUseCase(membershipRepo, driverRepo, teamRepo, logger),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, TEAM_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
useFactory: (membershipRepo: ITeamMembershipRepository, driverRepo: IDriverRepository, teamRepo: ITeamRepository) =>
new GetTeamJoinRequestsUseCase(membershipRepo, driverRepo, teamRepo),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, TEAM_REPOSITORY_TOKEN],
},
{
provide: CREATE_TEAM_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new CreateTeamUseCase(teamRepo, membershipRepo, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: UPDATE_TEAM_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
new UpdateTeamUseCase(teamRepo, membershipRepo),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
},
{
provide: GET_DRIVER_TEAM_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new GetDriverTeamUseCase(teamRepo, membershipRepo, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
useFactory: (membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new GetTeamMembershipUseCase(membershipRepo, logger),
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
{
provide: JOIN_TEAM_USE_CASE_TOKEN,
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
new JoinTeamUseCase(teamRepo, membershipRepo, logger),
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
},
];

View File

@@ -23,7 +23,7 @@ type TeamEntityStub = {
ownerId: ValueObjectStub;
leagues: ValueObjectStub[];
createdAt: { toDate(): Date };
logoRef: any;
logoRef: unknown;
category: string | undefined;
isRecruiting: boolean;
update: Mock;
@@ -124,13 +124,17 @@ describe('TeamService', () => {
clear: vi.fn(),
};
service = new TeamService(
teamRepository as unknown as never,
membershipRepository as unknown as never,
driverRepository as unknown as never,
logger,
teamStatsRepository as unknown as never
new GetAllTeamsUseCase(teamRepository as never, membershipRepository as never, teamStatsRepository as never, logger),
new GetTeamDetailsUseCase(teamRepository as never, membershipRepository as never),
new GetTeamMembersUseCase(membershipRepository as never, driverRepository as never, teamRepository as never, logger),
new GetTeamJoinRequestsUseCase(membershipRepository as never, driverRepository as never, teamRepository as never),
new CreateTeamUseCase(teamRepository as never, membershipRepository as never, logger),
new UpdateTeamUseCase(teamRepository as never, membershipRepository as never),
new GetDriverTeamUseCase(teamRepository as never, membershipRepository as never, logger),
new GetTeamMembershipUseCase(membershipRepository as never, logger),
{ execute: vi.fn() } as never, // joinTeamUseCase
logger
);
});

View File

@@ -12,9 +12,6 @@ import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
// Core imports
import type { Logger } from '@core/shared/application/Logger';
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
// Use cases
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
@@ -28,39 +25,44 @@ import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/Get
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
// Tokens
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN } from './TeamTokens';
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
import {
LOGGER_TOKEN,
GET_ALL_TEAMS_USE_CASE_TOKEN,
GET_TEAM_DETAILS_USE_CASE_TOKEN,
GET_TEAM_MEMBERS_USE_CASE_TOKEN,
GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
CREATE_TEAM_USE_CASE_TOKEN,
UPDATE_TEAM_USE_CASE_TOKEN,
GET_DRIVER_TEAM_USE_CASE_TOKEN,
GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
JOIN_TEAM_USE_CASE_TOKEN,
} from './TeamTokens';
@Injectable()
export class TeamService {
constructor(
@Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository,
@Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository,
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
@Inject(GET_ALL_TEAMS_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
@Inject(GET_TEAM_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase,
@Inject(GET_TEAM_MEMBERS_USE_CASE_TOKEN) private readonly getTeamMembersUseCase: GetTeamMembersUseCase,
@Inject(GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN) private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase,
@Inject(CREATE_TEAM_USE_CASE_TOKEN) private readonly createTeamUseCase: CreateTeamUseCase,
@Inject(UPDATE_TEAM_USE_CASE_TOKEN) private readonly updateTeamUseCase: UpdateTeamUseCase,
@Inject(GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
@Inject(GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN) private readonly getTeamMembershipUseCase: GetTeamMembershipUseCase,
@Inject(JOIN_TEAM_USE_CASE_TOKEN) private readonly joinTeamUseCase: JoinTeamUseCase,
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
) {}
async getAll(): Promise<GetAllTeamsOutputDTO> {
this.logger.debug('[TeamService] Fetching all teams.');
const useCase = new GetAllTeamsUseCase(
this.teamRepository,
this.membershipRepository,
this.teamStatsRepository,
this.logger
);
const result = await useCase.execute({});
const result = await this.getAllTeamsUseCase.execute({});
if (result.isErr()) {
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error'));
return { teams: [], totalCount: 0 };
}
const value = result.value;
if (!value) {
this.logger.error('Error fetching all teams', new Error(result.unwrapErr().details?.message || 'Unknown error'));
return { teams: [], totalCount: 0 };
}
const value = result.unwrap();
return {
teams: value.teams.map(t => ({
id: t.team.id,
@@ -86,18 +88,13 @@ export class TeamService {
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository);
const result = await useCase.execute({ teamId, driverId: userId || '' });
const result = await this.getTeamDetailsUseCase.execute({ teamId, driverId: userId || '' });
if (result.isErr()) {
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
return null;
}
const value = result.value;
if (!value) {
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return null;
}
const value = result.unwrap();
// Convert to DTO
return {
team: {
@@ -123,21 +120,9 @@ export class TeamService {
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger);
const result = await useCase.execute({ teamId });
const result = await this.getTeamMembersUseCase.execute({ teamId });
if (result.isErr()) {
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
return {
members: [],
totalCount: 0,
ownerCount: 0,
managerCount: 0,
memberCount: 0,
};
}
const value = result.value;
if (!value) {
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return {
members: [],
totalCount: 0,
@@ -147,6 +132,7 @@ export class TeamService {
};
}
const value = result.unwrap();
return {
members: value.members.map(m => ({
driverId: m.driver?.id || '',
@@ -166,19 +152,9 @@ export class TeamService {
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository);
const result = await useCase.execute({ teamId });
const result = await this.getTeamJoinRequestsUseCase.execute({ teamId });
if (result.isErr()) {
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error'));
return {
requests: [],
pendingCount: 0,
totalCount: 0,
};
}
const value = result.value;
if (!value) {
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.unwrapErr().details?.message || 'Unknown error'));
return {
requests: [],
pendingCount: 0,
@@ -186,6 +162,7 @@ export class TeamService {
};
}
const value = result.unwrap();
return {
requests: value.joinRequests.map(r => ({
requestId: r.id,
@@ -212,18 +189,13 @@ export class TeamService {
leagues: [],
};
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
const result = await useCase.execute(command);
const result = await this.createTeamUseCase.execute(command);
if (result.isErr()) {
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`);
return { id: '', success: false };
}
const value = result.value;
if (!value) {
this.logger.error(`Error creating team: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return { id: '', success: false };
}
const value = result.unwrap();
return { id: value.team.id, success: true };
}
@@ -240,10 +212,9 @@ export class TeamService {
updatedBy: userId || '',
};
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository);
const result = await useCase.execute(command);
const result = await this.updateTeamUseCase.execute(command);
if (result.isErr()) {
this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
this.logger.error(`Error updating team ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return { success: false };
}
@@ -253,14 +224,13 @@ export class TeamService {
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
const result = await useCase.execute({ driverId });
const result = await this.getDriverTeamUseCase.execute({ driverId });
if (result.isErr()) {
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return null;
}
const value = result.value;
const value = result.unwrap();
if (!value || !value.team) {
return null;
}
@@ -290,18 +260,13 @@ export class TeamService {
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger);
const result = await useCase.execute({ teamId, driverId });
const result = await this.getTeamMembershipUseCase.execute({ teamId, driverId });
if (result.isErr()) {
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
return null;
}
const value = result.value;
if (!value) {
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return null;
}
const value = result.unwrap();
return value.membership ? {
role: value.membership.role,
joinedAt: value.membership.joinedAt,
@@ -316,10 +281,9 @@ export class TeamService {
throw new Error('User ID is required');
}
const useCase = new JoinTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
const result = await useCase.execute({ teamId, driverId: userId });
const result = await this.joinTeamUseCase.execute({ teamId, driverId: userId });
if (result.isErr()) {
const error = result.error;
const error = result.unwrapErr();
this.logger.error(`Error joining team ${teamId}: ${error?.details?.message || 'Unknown error'}`);
return { success: false, error: error?.details?.message || 'Unknown error' };
}

View File

@@ -6,4 +6,14 @@ export const LOGGER_TOKEN = 'Logger';
export const TEAM_STATS_REPOSITORY_TOKEN = 'ITeamStatsRepository';
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
export const GET_ALL_TEAMS_USE_CASE_TOKEN = Symbol('GET_ALL_TEAMS_USE_CASE_TOKEN');
export const GET_TEAM_DETAILS_USE_CASE_TOKEN = Symbol('GET_TEAM_DETAILS_USE_CASE_TOKEN');
export const GET_TEAM_MEMBERS_USE_CASE_TOKEN = Symbol('GET_TEAM_MEMBERS_USE_CASE_TOKEN');
export const GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN = Symbol('GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN');
export const CREATE_TEAM_USE_CASE_TOKEN = Symbol('CREATE_TEAM_USE_CASE_TOKEN');
export const UPDATE_TEAM_USE_CASE_TOKEN = Symbol('UPDATE_TEAM_USE_CASE_TOKEN');
export const GET_DRIVER_TEAM_USE_CASE_TOKEN = Symbol('GET_DRIVER_TEAM_USE_CASE_TOKEN');
export const GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN = Symbol('GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN');
export const JOIN_TEAM_USE_CASE_TOKEN = Symbol('JOIN_TEAM_USE_CASE_TOKEN');

View File

@@ -12,7 +12,7 @@ describe('CreateTeamPresenter', () => {
const result = {
team: {
id: 'team-123',
} as any,
} as never,
};
presenter.present(result);
@@ -27,7 +27,7 @@ describe('CreateTeamPresenter', () => {
const result = {
team: {
id: 'team-456',
} as any,
} as never,
};
presenter.present(result);
@@ -41,7 +41,7 @@ describe('CreateTeamPresenter', () => {
describe('reset', () => {
it('should clear the model', () => {
presenter.present({ team: { id: 'team-123' } } as any);
presenter.present({ team: { id: 'team-123' } } as never);
expect(presenter.responseModel).toBeDefined();
presenter.reset();
@@ -55,7 +55,7 @@ describe('CreateTeamPresenter', () => {
});
it('should return model after present()', () => {
presenter.present({ team: { id: 'team-123' } } as any);
presenter.present({ team: { id: 'team-123' } } as never);
expect(presenter.getResponseModel()).toEqual({
id: 'team-123',
success: true,
@@ -69,7 +69,7 @@ describe('CreateTeamPresenter', () => {
});
it('should return model after present()', () => {
presenter.present({ team: { id: 'team-123' } } as any);
presenter.present({ team: { id: 'team-123' } } as never);
expect(presenter.responseModel).toEqual({
id: 'team-123',
success: true,

View File

@@ -13,19 +13,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team Alpha' } as any,
tag: { toString: () => 'TA' } as any,
description: { toString: () => 'Best team' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [{ toString: () => 'league-1' } as any],
name: { toString: () => 'Team Alpha' } as never,
tag: { toString: () => 'TA' } as never,
description: { toString: () => 'Best team' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [{ toString: () => 'league-1' } as never],
isRecruiting: true,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-01') } as never,
} as never,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
} as never,
};
presenter.present(result);
@@ -57,19 +57,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-456',
team: {
id: 'team-789',
name: { toString: () => 'Team Beta' } as any,
tag: { toString: () => 'TB' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
name: { toString: () => 'Team Beta' } as never,
tag: { toString: () => 'TB' } as never,
description: { toString: () => '' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [] as never[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-02') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-02') } as never,
} as never,
membership: {
role: 'manager' as const,
joinedAt: new Date('2024-01-02'),
status: 'active' as const,
} as any,
} as never,
};
presenter.present(result);
@@ -85,19 +85,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-789',
team: {
id: 'team-abc',
name: { toString: () => 'Team Gamma' } as any,
tag: { toString: () => 'TG' } as any,
description: { toString: () => 'Test team' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [{ toString: () => 'league-2' } as any],
name: { toString: () => 'Team Gamma' } as never,
tag: { toString: () => 'TG' } as never,
description: { toString: () => 'Test team' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [{ toString: () => 'league-2' } as never],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-03') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-03') } as never,
} as never,
membership: {
role: 'driver' as const,
joinedAt: new Date('2024-01-03'),
status: 'active' as const,
} as any,
} as never,
};
presenter.present(result);
@@ -113,19 +113,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
name: { toString: () => 'Team' } as never,
tag: { toString: () => 'T' } as never,
description: { toString: () => '' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [] as never[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-01') } as never,
} as never,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
} as never,
};
presenter.present(result);
@@ -139,19 +139,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => 'Test' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
name: { toString: () => 'Team' } as never,
tag: { toString: () => 'T' } as never,
description: { toString: () => 'Test' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [] as never[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-01') } as never,
} as never,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
} as never,
};
presenter.present(result);
@@ -167,19 +167,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
name: { toString: () => 'Team' } as never,
tag: { toString: () => 'T' } as never,
description: { toString: () => '' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [] as never[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-01') } as never,
} as never,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
} as never,
});
expect(presenter.getResponseModel()).toBeDefined();
@@ -198,19 +198,19 @@ describe('DriverTeamPresenter', () => {
driverId: 'driver-123',
team: {
id: 'team-456',
name: { toString: () => 'Team' } as any,
tag: { toString: () => 'T' } as any,
description: { toString: () => '' } as any,
ownerId: { toString: () => 'driver-123' } as any,
leagues: [] as any[],
name: { toString: () => 'Team' } as never,
tag: { toString: () => 'T' } as never,
description: { toString: () => '' } as never,
ownerId: { toString: () => 'driver-123' } as never,
leagues: [] as never[],
isRecruiting: false,
createdAt: { toDate: () => new Date('2024-01-01') } as any,
} as any,
createdAt: { toDate: () => new Date('2024-01-01') } as never,
} as never,
membership: {
role: 'owner' as const,
joinedAt: new Date('2024-01-01'),
status: 'active' as const,
} as any,
} as never,
});
expect(presenter.getResponseModel()).not.toBeNull();
});

View File

@@ -75,7 +75,7 @@ export function getForceReseed(): boolean {
/**
* When set, the API will generate `openapi.json` and optionally reduce logging noise.
*
* Matches previous behavior: any value (even "0") counts as enabled if the var is present.
* Matches previous behavior: unknown value (even "0") counts as enabled if the var is present.
*/
export function getGenerateOpenapi(): boolean {
return isSet(process.env.GENERATE_OPENAPI);

View File

@@ -13,7 +13,7 @@ vi.mock('../config/feature-loader', () => ({
import { loadFeatureConfig } from '../config/feature-loader';
describe('Features HTTP Endpoint', () => {
let app: any;
let app: import("@nestjs/common").INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({

View File

@@ -0,0 +1,34 @@
import { Result } from '@core/shared/application/Result';
import type { MediaStoragePort } from '../ports/MediaStoragePort';
export type GetUploadedMediaInput = {
storageKey: string;
};
export type GetUploadedMediaResult = {
bytes: Buffer;
contentType: string;
};
export class GetUploadedMediaUseCase {
constructor(private readonly mediaStorage: MediaStoragePort) {}
async execute(input: GetUploadedMediaInput): Promise<Result<GetUploadedMediaResult | null>> {
try {
const bytes = await this.mediaStorage.getBytes!(input.storageKey);
if (!bytes) {
return Result.ok(null);
}
const metadata = await this.mediaStorage.getMetadata!(input.storageKey);
const contentType = metadata?.contentType || 'application/octet-stream';
return Result.ok({
bytes: Buffer.from(bytes),
contentType,
});
} catch (error) {
return Result.err(error instanceof Error ? error : new Error(String(error)));
}
}
}

View File

@@ -0,0 +1,20 @@
import { Result } from '@core/shared/application/Result';
import type { MediaReference } from '@core/domain/media/MediaReference';
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
export type ResolveMediaReferenceInput = {
reference: MediaReference;
};
export class ResolveMediaReferenceUseCase {
constructor(private readonly mediaResolver: MediaResolverPort) {}
async execute(input: ResolveMediaReferenceInput): Promise<Result<string | null>> {
try {
const resolvedPath = await this.mediaResolver.resolve(input.reference);
return Result.ok(resolvedPath);
} catch (error) {
return Result.err(error instanceof Error ? error : new Error(String(error)));
}
}
}

View File

@@ -0,0 +1,63 @@
/**
* Application Use Case: GetAllNotificationsUseCase
*
* Retrieves all notifications for a recipient.
*/
import type { Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Notification } from '../../domain/entities/Notification';
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
export type GetAllNotificationsInput = {
recipientId: string;
};
export interface GetAllNotificationsResult {
notifications: Notification[];
totalCount: number;
}
export type GetAllNotificationsErrorCode = 'REPOSITORY_ERROR';
export class GetAllNotificationsUseCase {
constructor(
private readonly notificationRepository: INotificationRepository,
private readonly logger: Logger,
) {}
async execute(
input: GetAllNotificationsInput,
): Promise<Result<GetAllNotificationsResult, ApplicationErrorCode<GetAllNotificationsErrorCode, { message: string }>>> {
const { recipientId } = input;
this.logger.debug(
`Attempting to retrieve all notifications for recipient ID: ${recipientId}`,
);
try {
const notifications = await this.notificationRepository.findByRecipientId(
recipientId,
);
this.logger.info(
`Successfully retrieved ${notifications.length} notifications for recipient ID: ${recipientId}`,
);
return Result.ok<GetAllNotificationsResult, ApplicationErrorCode<GetAllNotificationsErrorCode, { message: string }>>({
notifications,
totalCount: notifications.length,
});
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
this.logger.error(
`Failed to retrieve notifications for recipient ID: ${recipientId}`,
err,
);
return Result.err<GetAllNotificationsResult, ApplicationErrorCode<GetAllNotificationsErrorCode, { message: string }>>({
code: 'REPOSITORY_ERROR',
details: { message: err.message },
});
}
}
}

View File

@@ -0,0 +1,20 @@
import { Result } from '@core/shared/application/Result';
import type { Driver } from '../../domain/entities/Driver';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
export type GetDriverInput = {
driverId: string;
};
export class GetDriverUseCase {
constructor(private readonly driverRepository: IDriverRepository) {}
async execute(input: GetDriverInput): Promise<Result<Driver | null>> {
try {
const driver = await this.driverRepository.findById(input.driverId);
return Result.ok(driver);
} catch (error) {
return Result.err(error instanceof Error ? error : new Error(String(error)));
}
}
}