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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ describe('AnalyticsController', () => {
recordEngagement: vi.fn(), recordEngagement: vi.fn(),
getDashboardData: vi.fn(), getDashboardData: vi.fn(),
getAnalyticsMetrics: vi.fn(), getAnalyticsMetrics: vi.fn(),
} as any; } as never;
controller = new AnalyticsController(service); controller = new AnalyticsController(service);
}); });
@@ -121,7 +121,7 @@ describe('AnalyticsController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -168,9 +168,9 @@ describe('AnalyticsController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

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

View File

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

View File

@@ -118,7 +118,7 @@ describe('AuthController', () => {
}; };
(service.getCurrentSession as Mock).mockResolvedValue(session); (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); await controller.getSession(res);
@@ -129,7 +129,7 @@ describe('AuthController', () => {
it('should write JSON null when no session', async () => { it('should write JSON null when no session', async () => {
(service.getCurrentSession as Mock).mockResolvedValue(null); (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); await controller.getSession(res);
@@ -151,7 +151,7 @@ describe('AuthController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -201,9 +201,9 @@ describe('AuthController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -45,7 +45,7 @@ type SessionPort = {
}; };
describe('Auth guards (HTTP)', () => { describe('Auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: SessionPort = { const sessionPort: SessionPort = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -75,9 +75,9 @@ describe('Auth guards (HTTP)', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

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

View File

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

View File

@@ -11,44 +11,44 @@ function createExecutionContext(request: Record<string, unknown>) {
describe('AuthenticationGuard', () => { describe('AuthenticationGuard', () => {
it('attaches request.user.userId from session when missing', async () => { it('attaches request.user.userId from session when missing', async () => {
const request: any = {}; const request: unknown = {};
const sessionPort = { const sessionPort = {
getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })), 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(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 () => { 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 = { const sessionPort = {
getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })), 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(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 () => { it('leaves request.user undefined when no session exists', async () => {
const request: any = {}; const request: unknown = {};
const sessionPort = { const sessionPort = {
getCurrentSession: vi.fn(async () => null), 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(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', () => { describe('AuthorizationGuard', () => {
it('allows public routes without a user session', async () => { it('allows public routes without a user session', async () => {
const authorizationService = { getRolesForUser: vi.fn() }; 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({ const ctx = createExecutionContext({
handler: DummyController.prototype.publicHandler, 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(); expect(authorizationService.getRolesForUser).not.toHaveBeenCalled();
}); });
it('denies non-public routes by default when not authenticated', async () => { it('denies non-public routes by default when not authenticated', async () => {
const authorizationService = { getRolesForUser: vi.fn() }; 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({ const ctx = createExecutionContext({
handler: DummyController.prototype.protectedHandler, 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 () => { it('allows non-public routes when authenticated', async () => {
const authorizationService = { getRolesForUser: vi.fn().mockReturnValue([]) }; 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({ const ctx = createExecutionContext({
handler: DummyController.prototype.protectedHandler, handler: DummyController.prototype.protectedHandler,
userId: 'user-1', 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 () => { it('denies routes requiring roles when user does not have any required role', async () => {
const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['user']) }; 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({ const ctx = createExecutionContext({
handler: DummyController.prototype.adminHandler, handler: DummyController.prototype.adminHandler,
userId: 'user-1', 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 () => { it('allows routes requiring roles when user has a required role', async () => {
const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['admin']) }; 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({ const ctx = createExecutionContext({
handler: DummyController.prototype.adminHandler, handler: DummyController.prototype.adminHandler,
userId: 'user-1', 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 seedDemoUsersExecute = vi.fn(async () => undefined);
const bootstrapModule = new BootstrapModule( const bootstrapModule = new BootstrapModule(
{ execute: ensureExecute } as any, { execute: ensureExecute } as never,
{ 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,
{ {
leagueRepository: { countAll: leagueCountAll }, leagueRepository: { countAll: leagueCountAll },
} as any, } as never,
{ execute: seedDemoUsersExecute } as any, { execute: seedDemoUsersExecute } as never,
); );
await bootstrapModule.onModuleInit(); await bootstrapModule.onModuleInit();

View File

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

View File

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

View File

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

View File

@@ -53,7 +53,7 @@ describe('DashboardController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -108,9 +108,9 @@ describe('DashboardController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -9,9 +9,9 @@ describe('DashboardService', () => {
const useCase = { execute: vi.fn(async () => Result.ok(mockResult)) }; const useCase = { execute: vi.fn(async () => Result.ok(mockResult)) };
const service = new DashboardService( const service = new DashboardService(
{ 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,
useCase as any, useCase as never,
presenter as any, presenter as never,
); );
await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] }); await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] });
@@ -21,9 +21,9 @@ describe('DashboardService', () => {
it('getDashboardOverview throws with details message on error', async () => { it('getDashboardOverview throws with details message on error', async () => {
const service = new DashboardService( const service = new DashboardService(
{ 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,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ present: vi.fn(), getResponseModel: vi.fn() } as any, { present: vi.fn(), getResponseModel: vi.fn() } as never,
); );
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: boom'); 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 () => { it('getDashboardOverview throws with fallback message when no details.message', async () => {
const service = new DashboardService( const service = new DashboardService(
{ 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,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ present: vi.fn(), getResponseModel: vi.fn() } as any, { present: vi.fn(), getResponseModel: vi.fn() } as never,
); );
await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: Unknown error'); 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)', () => { describe('Driver domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();
@@ -62,9 +62,9 @@ describe('Driver domain (HTTP, module-wiring)', () => {
}; };
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -186,7 +186,7 @@ describe('DriverController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -225,9 +225,9 @@ describe('DriverController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); 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 { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase';
import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase'; import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase';
import { GetDriverLiveriesUseCase } from '@core/racing/application/use-cases/GetDriverLiveriesUseCase'; 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 { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase';
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase'; import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase'; import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
@@ -67,6 +68,7 @@ import {
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
GET_TOTAL_DRIVERS_USE_CASE_TOKEN, GET_TOTAL_DRIVERS_USE_CASE_TOKEN,
GET_DRIVER_LIVERIES_USE_CASE_TOKEN, GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
GET_DRIVER_USE_CASE_TOKEN,
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
@@ -276,4 +278,9 @@ export const DriverProviders: Provider[] = createLoggedProviders([
new GetDriverLiveriesUseCase(liveryRepository, logger), new GetDriverLiveriesUseCase(liveryRepository, logger),
inject: [LIVERY_REPOSITORY_TOKEN, LOGGER_TOKEN], inject: [LIVERY_REPOSITORY_TOKEN, LOGGER_TOKEN],
}, },
{
provide: GET_DRIVER_USE_CASE_TOKEN,
useFactory: (driverRepo: IDriverRepository) => new GetDriverUseCase(driverRepo),
inject: [DRIVER_REPOSITORY_TOKEN],
},
], initLogger); ], initLogger);

View File

@@ -14,9 +14,7 @@ describe('DriverService', () => {
const isDriverRegisteredForRaceUseCase = { execute: vi.fn() }; const isDriverRegisteredForRaceUseCase = { execute: vi.fn() };
const updateDriverProfileUseCase = { execute: vi.fn() }; const updateDriverProfileUseCase = { execute: vi.fn() };
const getProfileOverviewUseCase = { execute: vi.fn() }; const getProfileOverviewUseCase = { execute: vi.fn() };
const getDriverUseCase = { execute: vi.fn() };
// Mock for repository
const driverRepository = { findById: vi.fn() };
// Mocks for presenters // Mocks for presenters
const driversLeaderboardPresenter = { present: vi.fn(), getResponseModel: vi.fn() }; const driversLeaderboardPresenter = { present: vi.fn(), getResponseModel: vi.fn() };
@@ -38,8 +36,7 @@ describe('DriverService', () => {
isDriverRegisteredForRaceUseCase.execute.mockResolvedValue(Result.ok(undefined)); isDriverRegisteredForRaceUseCase.execute.mockResolvedValue(Result.ok(undefined));
updateDriverProfileUseCase.execute.mockResolvedValue(Result.ok(undefined)); updateDriverProfileUseCase.execute.mockResolvedValue(Result.ok(undefined));
getProfileOverviewUseCase.execute.mockResolvedValue(Result.ok(undefined)); getProfileOverviewUseCase.execute.mockResolvedValue(Result.ok(undefined));
getDriverUseCase.execute.mockResolvedValue(Result.ok(null));
driverRepository.findById.mockResolvedValue(null);
driversLeaderboardPresenter.getResponseModel.mockReturnValue({ items: [] }); driversLeaderboardPresenter.getResponseModel.mockReturnValue({ items: [] });
driverStatsPresenter.getResponseModel.mockReturnValue({ totalDrivers: 0 }); driverStatsPresenter.getResponseModel.mockReturnValue({ totalDrivers: 0 });
@@ -52,22 +49,22 @@ describe('DriverService', () => {
const createService = () => { const createService = () => {
return new DriverService( return new DriverService(
getDriversLeaderboardUseCase as any, getDriversLeaderboardUseCase as never,
getTotalDriversUseCase as any, getTotalDriversUseCase as never,
getDriverLiveriesUseCase as any, getDriverLiveriesUseCase as never,
completeDriverOnboardingUseCase as any, completeDriverOnboardingUseCase as never,
isDriverRegisteredForRaceUseCase as any, isDriverRegisteredForRaceUseCase as never,
updateDriverProfileUseCase as any, updateDriverProfileUseCase as never,
getProfileOverviewUseCase as any, getProfileOverviewUseCase as never,
driverRepository as any, getDriverUseCase as never,
logger as any, logger as never,
driversLeaderboardPresenter as any, driversLeaderboardPresenter as never,
driverStatsPresenter as any, driverStatsPresenter as never,
completeOnboardingPresenter as any, completeOnboardingPresenter as never,
driverRegistrationStatusPresenter as any, driverRegistrationStatusPresenter as never,
driverPresenter as any, driverPresenter as never,
driverProfilePresenter as any, driverProfilePresenter as never,
getDriverLiveriesPresenter as any, getDriverLiveriesPresenter as never,
); );
}; };
@@ -97,7 +94,7 @@ describe('DriverService', () => {
lastName: 'L', lastName: 'L',
displayName: 'D', displayName: 'D',
country: 'DE', country: 'DE',
} as any); } as never);
expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({ expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({
userId: 'u1', userId: 'u1',
@@ -115,7 +112,7 @@ describe('DriverService', () => {
displayName: 'D', displayName: 'D',
country: 'DE', country: 'DE',
bio: 'bio', bio: 'bio',
} as any); } as never);
expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({ expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({
userId: 'u1', userId: 'u1',
@@ -132,19 +129,19 @@ describe('DriverService', () => {
driverRegistrationStatusPresenter.getResponseModel.mockReturnValue({ isRegistered: true }); driverRegistrationStatusPresenter.getResponseModel.mockReturnValue({ isRegistered: true });
await expect( await expect(
service.getDriverRegistrationStatus({ raceId: 'r1', driverId: 'd1' } as any), service.getDriverRegistrationStatus({ raceId: 'r1', driverId: 'd1' } as never),
).resolves.toEqual({ isRegistered: true }); ).resolves.toEqual({ isRegistered: true });
expect(isDriverRegisteredForRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', driverId: 'd1' }); expect(isDriverRegisteredForRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', driverId: 'd1' });
expect(driverRegistrationStatusPresenter.getResponseModel).toHaveBeenCalled(); 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(); const service = createService();
driverRepository.findById.mockResolvedValue(null); getDriverUseCase.execute.mockResolvedValue(Result.ok(null));
await expect(service.getCurrentDriver('u1')).resolves.toBeNull(); await expect(service.getCurrentDriver('u1')).resolves.toBeNull();
expect(driverRepository.findById).toHaveBeenCalledWith('u1'); expect(getDriverUseCase.execute).toHaveBeenCalledWith({ driverId: 'u1' });
expect(driverPresenter.getResponseModel).toHaveBeenCalled(); expect(driverPresenter.getResponseModel).toHaveBeenCalled();
}); });
@@ -173,12 +170,12 @@ describe('DriverService', () => {
expect(driverPresenter.getResponseModel).toHaveBeenCalled(); 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(); const service = createService();
driverRepository.findById.mockResolvedValue(null); getDriverUseCase.execute.mockResolvedValue(Result.ok(null));
await expect(service.getDriver('d1')).resolves.toBeNull(); 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 () => { 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 { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase'; import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
import { UpdateDriverProfileUseCase, type UpdateDriverProfileInput } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase'; import { UpdateDriverProfileUseCase, type UpdateDriverProfileInput } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
import { GetDriverUseCase } from '@core/racing/application/use-cases/GetDriverUseCase';
// Presenters // Presenters
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter'; import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';
@@ -29,11 +30,9 @@ import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter'; import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter';
// Tokens // Tokens
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
import type { Logger } from '@core/shared/application'; import type { Logger } from '@core/shared/application';
import { import {
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
DRIVER_REPOSITORY_TOKEN,
GET_DRIVER_LIVERIES_USE_CASE_TOKEN, GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN, GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
@@ -41,6 +40,7 @@ import {
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
LOGGER_TOKEN, LOGGER_TOKEN,
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
GET_DRIVER_USE_CASE_TOKEN,
} from './DriverTokens'; } from './DriverTokens';
@Injectable() @Injectable()
@@ -60,8 +60,8 @@ export class DriverService {
private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase, private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
@Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN) @Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN)
private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase, private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase,
@Inject(DRIVER_REPOSITORY_TOKEN) @Inject(GET_DRIVER_USE_CASE_TOKEN)
private readonly driverRepository: IDriverRepository, // TODO must be removed from service private readonly getDriverUseCase: GetDriverUseCase,
@Inject(LOGGER_TOKEN) @Inject(LOGGER_TOKEN)
private readonly logger: Logger, private readonly logger: Logger,
// Injected presenters (optional for module test compatibility) // Injected presenters (optional for module test compatibility)
@@ -138,8 +138,11 @@ export class DriverService {
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> { async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`); this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
const driver = await this.driverRepository.findById(userId); const result = await this.getDriverUseCase.execute({ driverId: userId });
await this.driverPresenter!.present(Result.ok(driver)); if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
await this.driverPresenter!.present(Result.ok(result.unwrap()));
return this.driverPresenter!.getResponseModel(); return this.driverPresenter!.getResponseModel();
} }
@@ -157,15 +160,22 @@ export class DriverService {
await this.updateDriverProfileUseCase.execute(input); await this.updateDriverProfileUseCase.execute(input);
// Get the updated driver and present it // Get the updated driver and present it
const driver = await this.driverRepository.findById(driverId); const result = await this.getDriverUseCase.execute({ driverId });
await this.driverPresenter!.present(Result.ok(driver)); if (result.isErr()) {
throw new Error(result.unwrapErr().message);
}
await this.driverPresenter!.present(Result.ok(result.unwrap()));
return this.driverPresenter!.getResponseModel(); return this.driverPresenter!.getResponseModel();
} }
async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> { async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`); 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) { if (!driver) {
return null; return null;
} }
@@ -193,4 +203,4 @@ export class DriverService {
await this.getDriverLiveriesPresenter!.present(result); await this.getDriverLiveriesPresenter!.present(result);
return this.getDriverLiveriesPresenter!.getResponseModel()!; 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 UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase';
export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase'; export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase';
export const GET_DRIVER_LIVERIES_USE_CASE_TOKEN = 'GetDriverLiveriesUseCase'; 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_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN = 'GetDriversLeaderboardOutputPort_TOKEN';
export const GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN = 'GetTotalDriversOutputPort_TOKEN'; export const GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN = 'GetTotalDriversOutputPort_TOKEN';

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard';
describe('League roster admin read (HTTP, league-scoped)', () => { describe('League roster admin read (HTTP, league-scoped)', () => {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();
@@ -62,9 +62,9 @@ describe('League roster admin read (HTTP, league-scoped)', () => {
}; };
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); 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).toEqual(expect.any(Array));
expect(res.body.length).toBeGreaterThan(0); 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({ expect(first).toMatchObject({
driverId: expect.any(String), driverId: expect.any(String),
role: 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. // Seed data may or may not include join requests for a given league.
// Validate shape on first item if present. // Validate shape on first item if present.
if ((res.body as any[]).length > 0) { if ((res.body as never[]).length > 0) {
const first = (res.body as any[])[0]; const first = (res.body as unknown as { message?: string }[])[0];
expect(first).toMatchObject({ expect(first).toMatchObject({
id: expect.any(String), id: expect.any(String),
leagueId: expect.any(String), leagueId: expect.any(String),
@@ -146,8 +146,10 @@ describe('League roster admin read (HTTP, league-scoped)', () => {
}, },
}); });
if (first.message !== undefined) { if (first) {
expect(first.message).toEqual(expect.any(String)); 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'; import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
describe('League roster join request mutations (HTTP)', () => { 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 } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -123,15 +123,16 @@ describe('League roster join request mutations (HTTP)', () => {
app = module.createNestApplication(); app = module.createNestApplication();
// Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner(). // 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. // Test-only auth injection: emulate an authenticated session by setting request.user.
app.use((req: any, _res: any, next: any) => { app.use((req: unknown, _res: unknown, next: unknown) => {
const userId = req.headers['x-test-user-id']; 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) { if (typeof userId === 'string' && userId.length > 0) {
req.user = { userId }; r.user = { userId };
} }
next(); (next as () => void)();
}); });
app.useGlobalPipes( app.useGlobalPipes(
@@ -144,8 +145,8 @@ describe('League roster join request mutations (HTTP)', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
); );
await app.init(); await app.init();
@@ -202,7 +203,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200); .expect(200);
expect(Array.isArray(joinRequests.body)).toBe(true); 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()) const members = await request(app.getHttpServer())
.get('/leagues/league-1/admin/roster/members') .get('/leagues/league-1/admin/roster/members')
@@ -210,7 +211,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200); .expect(200);
expect(Array.isArray(members.body)).toBe(true); 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 () => { it('reject removes request only; roster reads reflect changes', async () => {
@@ -232,7 +233,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200); .expect(200);
expect(Array.isArray(joinRequests.body)).toBe(true); 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()) const members = await request(app.getHttpServer())
.get('/leagues/league-1/admin/roster/members') .get('/leagues/league-1/admin/roster/members')
@@ -240,7 +241,7 @@ describe('League roster join request mutations (HTTP)', () => {
.expect(200); .expect(200);
expect(Array.isArray(members.body)).toBe(true); 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 () => { 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(200);
expect(Array.isArray(joinRequests.body)).toBe(true); 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'; import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
describe('League roster member mutations (HTTP)', () => { 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 } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -98,15 +98,16 @@ describe('League roster member mutations (HTTP)', () => {
app = module.createNestApplication(); app = module.createNestApplication();
// Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner(). // 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. // Test-only auth injection: emulate an authenticated session by setting request.user.
app.use((req: any, _res: any, next: any) => { app.use((req: unknown, _res: unknown, next: unknown) => {
const userId = req.headers['x-test-user-id']; 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) { if (typeof userId === 'string' && userId.length > 0) {
req.user = { userId }; r.user = { userId };
} }
next(); (next as () => void)();
}); });
app.useGlobalPipes( app.useGlobalPipes(
@@ -119,8 +120,8 @@ describe('League roster member mutations (HTTP)', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
); );
await app.init(); await app.init();
@@ -173,9 +174,9 @@ describe('League roster member mutations (HTTP)', () => {
expect(Array.isArray(members.body)).toBe(true); 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).toBeDefined();
expect(updated.role).toBe('steward'); expect(updated?.role).toBe('steward');
}); });
it('member removal is reflected in roster members read', async () => { it('member removal is reflected in roster members read', async () => {
@@ -192,6 +193,6 @@ describe('League roster member mutations (HTTP)', () => {
.expect(200); .expect(200);
expect(Array.isArray(members.body)).toBe(true); 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)', () => { describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();
@@ -62,9 +62,9 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
}; };
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();
@@ -152,7 +152,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => {
const raceId: string = createRes.body.raceId; const raceId: string = createRes.body.raceId;
const afterCreateRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200); 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({ expect(createdRace).toMatchObject({
id: raceId, id: raceId,
name: 'Test Track - Test Car', 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 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({ expect(updatedRace).toMatchObject({
id: raceId, id: raceId,
name: 'Updated Track - Updated Car', 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 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(); expect(deletedRace).toBeUndefined();
}); });
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,28 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
} }
present(result: GetLeagueFullConfigResult): void { present(result: GetLeagueFullConfigResult): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const dto = result.config as unknown as {
const dto = result.config as any; 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 league = dto.league;
const settings = league.settings; const settings = league.settings;
const stewarding = dto.activeSeason?.stewardingConfig; const stewarding = dto.activeSeason?.stewardingConfig;
@@ -53,7 +73,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
}, },
dropPolicy: { dropPolicy: {
strategy: dropPolicy?.strategy === 'none' ? 'none' : 'worst_n', strategy: dropPolicy?.strategy === 'none' ? 'none' : 'worst_n',
n: dropPolicy?.n, ...(dropPolicy?.n !== undefined ? { n: dropPolicy.n } : {}),
}, },
timings: { timings: {
raceDayOfWeek, raceDayOfWeek,
@@ -69,7 +89,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
stewardingClosesHours: stewarding?.stewardingClosesHours ?? 168, stewardingClosesHours: stewarding?.stewardingClosesHours ?? 168,
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest ?? true, notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest ?? true,
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired ?? 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'), requestedAt: new Date('2023-01-01'),
message: 'Please accept me', message: 'Please accept me',
// eslint-disable-next-line @typescript-eslint/no-explicit-any // 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 presenter = new LeagueOwnerSummaryPresenter();
const output: GetLeagueOwnerSummaryResult = { const output: GetLeagueOwnerSummaryResult = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
league: {} as any, league: {} as never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
owner: { owner: {
id: 'driver-1', id: 'driver-1',
@@ -14,9 +14,9 @@ describe('LeagueOwnerSummaryPresenter', () => {
name: 'John Doe', name: 'John Doe',
country: 'US', country: 'US',
bio: 'Racing enthusiast', 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any, } as never,
totalMembers: 50, totalMembers: 50,
activeMembers: 45, activeMembers: 45,
rating: 1500, rating: 1500,

View File

@@ -19,23 +19,23 @@ describe('LeagueSchedulePresenter', () => {
seasonId: 'season-1', seasonId: 'season-1',
published: false, published: false,
races: [{ race }], 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).not.toBeNull();
expect(vm.seasonId).toBe('season-1'); expect(vm.seasonId).toBe('season-1');
expect(vm.published).toBe(false); expect(vm.published).toBe(false);
expect(Array.isArray(vm.races)).toBe(true); expect(Array.isArray(vm.races)).toBe(true);
expect(vm.races[0]).toMatchObject({ expect(vm.races[0]!).toMatchObject({
id: 'race-1', id: 'race-1',
name: 'Spa - GT3', name: 'Spa - GT3',
date: '2025-01-02T20:00:00.000Z', date: '2025-01-02T20:00:00.000Z',
}); });
// Guard: dates must be ISO strings (no Date objects) // Guard: dates must be ISO strings (no Date objects)
expect(typeof vm.races[0].date).toBe('string'); 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(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 }; const originalEnv = { ...process.env };
let module: TestingModule | undefined; let module: TestingModule | undefined;
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();

View File

@@ -35,6 +35,8 @@ describe('MediaController', () => {
deleteMedia: ReturnType<typeof vi.fn>; deleteMedia: ReturnType<typeof vi.fn>;
getAvatar: ReturnType<typeof vi.fn>; getAvatar: ReturnType<typeof vi.fn>;
updateAvatar: ReturnType<typeof vi.fn>; updateAvatar: ReturnType<typeof vi.fn>;
getUploadedMedia: ReturnType<typeof vi.fn>;
resolveMediaReference: ReturnType<typeof vi.fn>;
}; };
let generationService: MediaGenerationService & { let generationService: MediaGenerationService & {
generateDriverAvatar: ReturnType<typeof vi.fn>; generateDriverAvatar: ReturnType<typeof vi.fn>;
@@ -57,6 +59,8 @@ describe('MediaController', () => {
deleteMedia: vi.fn(), deleteMedia: vi.fn(),
getAvatar: vi.fn(), getAvatar: vi.fn(),
updateAvatar: vi.fn(), updateAvatar: vi.fn(),
getUploadedMedia: vi.fn(),
resolveMediaReference: vi.fn(),
}, },
}, },
{ {
@@ -97,8 +101,8 @@ describe('MediaController', () => {
}).compile(); }).compile();
controller = module.get<MediaController>(MediaController); controller = module.get<MediaController>(MediaController);
service = module.get(MediaService) as any; service = module.get(MediaService) as never;
generationService = module.get(MediaGenerationService) as any; generationService = module.get(MediaGenerationService) as never;
}); });
const createMockResponse = (): Response => { const createMockResponse = (): Response => {
@@ -375,6 +379,7 @@ describe('MediaController', () => {
}; };
const mockService = { const mockService = {
getMedia: vi.fn().mockResolvedValue({ id: mediaId }), getMedia: vi.fn().mockResolvedValue({ id: mediaId }),
getUploadedMedia: vi.fn().mockResolvedValue({ bytes: pngBuffer, contentType: 'image/png' }),
}; };
const module = await Test.createTestingModule({ const module = await Test.createTestingModule({
@@ -409,8 +414,7 @@ describe('MediaController', () => {
await testController.getUploadedMedia(mediaId, res); await testController.getUploadedMedia(mediaId, res);
expect(mockService.getMedia).toHaveBeenCalledWith(mediaId); expect(mockService.getMedia).toHaveBeenCalledWith(mediaId);
expect(mockStorage.getBytes).toHaveBeenCalledWith('uploaded/media-123'); expect(mockService.getUploadedMedia).toHaveBeenCalledWith('uploaded/media-123');
expect(mockStorage.getMetadata).toHaveBeenCalledWith('uploaded/media-123');
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/png'); expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/png');
expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', 'public, max-age=31536000, immutable'); expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', 'public, max-age=31536000, immutable');
expect(res.status).toHaveBeenCalledWith(200); expect(res.status).toHaveBeenCalledWith(200);
@@ -569,7 +573,7 @@ describe('MediaController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -603,6 +607,8 @@ describe('MediaController', () => {
uploadMedia: vi.fn(), uploadMedia: vi.fn(),
getAvatar: vi.fn(), getAvatar: vi.fn(),
updateAvatar: 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(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -19,9 +19,7 @@ import type { MulterFile } from './types/MulterFile';
import { MediaGenerationService } from '@core/media/domain/services/MediaGenerationService'; import { MediaGenerationService } from '@core/media/domain/services/MediaGenerationService';
import type { Logger } from '@core/shared/application/Logger'; import type { Logger } from '@core/shared/application/Logger';
import { MediaReference } from '@core/domain/media/MediaReference'; import { MediaReference } from '@core/domain/media/MediaReference';
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter'; import { LOGGER_TOKEN } from './MediaTokens';
import { LOGGER_TOKEN, MEDIA_STORAGE_PORT_TOKEN } from './MediaTokens';
import type { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
@@ -36,8 +34,6 @@ export class MediaController {
@Inject(MediaService) private readonly mediaService: MediaService, @Inject(MediaService) private readonly mediaService: MediaService,
@Inject(MediaGenerationService) private readonly mediaGenerationService: MediaGenerationService, @Inject(MediaGenerationService) private readonly mediaGenerationService: MediaGenerationService,
@Inject(LOGGER_TOKEN) private readonly logger: Logger, @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') @Post('avatar/generate')
@@ -272,23 +268,19 @@ export class MediaController {
// The mediaId is used as the storage key // The mediaId is used as the storage key
const storageKey = `uploaded/${mediaId}`; const storageKey = `uploaded/${mediaId}`;
// Get file bytes from storage // Get file bytes from storage via service
const bytes = await this.mediaStorage.getBytes!(storageKey); const result = await this.mediaService.getUploadedMedia(storageKey);
if (!bytes) { if (!result) {
this.logger.warn('[MediaController] Uploaded media file not found', { mediaId, storageKey }); this.logger.warn('[MediaController] Uploaded media file not found', { mediaId, storageKey });
res.status(HttpStatus.NOT_FOUND).json({ error: 'Media file not found' }); res.status(HttpStatus.NOT_FOUND).json({ error: 'Media file not found' });
return; return;
} }
// Get metadata to determine content type res.setHeader('Content-Type', result.contentType);
const metadata = await this.mediaStorage.getMetadata!(storageKey);
const contentType = metadata?.contentType || 'application/octet-stream';
res.setHeader('Content-Type', contentType);
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); 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() @Public()
@@ -339,7 +331,7 @@ export class MediaController {
if (ref) { if (ref) {
refHash = ref.hash(); refHash = ref.hash();
resolvedPath = await this.mediaResolver.resolve(ref); resolvedPath = await this.mediaService.resolveMediaReference(ref);
if (!resolvedPath) { if (!resolvedPath) {
notes.push('Resolver returned null'); notes.push('Resolver returned null');
@@ -382,7 +374,7 @@ export class MediaController {
): Promise<void> { ): Promise<void> {
this.logger.debug('[MediaController] Getting media details', { mediaId }); this.logger.debug('[MediaController] Getting media details', { mediaId });
const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId); const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId);
if (dto) { if (dto) {
this.logger.info('[MediaController] Media details found', { mediaId }); this.logger.info('[MediaController] Media details found', { mediaId });
res.status(HttpStatus.OK).json(dto); 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 { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort'; import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
import type { Logger } from '@core/shared/application'; import type { Logger } from '@core/shared/application';
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
// Import use cases // Import use cases
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase'; 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 { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase'; import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase'; 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 presenters
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter'; import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
@@ -39,6 +42,9 @@ import {
DELETE_MEDIA_USE_CASE_TOKEN, DELETE_MEDIA_USE_CASE_TOKEN,
GET_AVATAR_USE_CASE_TOKEN, GET_AVATAR_USE_CASE_TOKEN,
UPDATE_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'; } from './MediaTokens';
export * from './MediaTokens'; export * from './MediaTokens';
@@ -92,7 +98,7 @@ const initLogger = InitializationLogger.getInstance();
export const MediaProviders: Provider[] = createLoggedProviders([ export const MediaProviders: Provider[] = createLoggedProviders([
MediaGenerationService, MediaGenerationService,
{ {
provide: MediaResolverAdapter, provide: MEDIA_RESOLVER_PORT_TOKEN,
useFactory: () => new MediaResolverAdapter({}), useFactory: () => new MediaResolverAdapter({}),
}, },
RequestAvatarGenerationPresenter, RequestAvatarGenerationPresenter,
@@ -156,4 +162,16 @@ export const MediaProviders: Provider[] = createLoggedProviders([
new UpdateAvatarUseCase(avatarRepo, logger), new UpdateAvatarUseCase(avatarRepo, logger),
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN], 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( const service = new MediaService(
requestAvatarGenerationUseCase as any, requestAvatarGenerationUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
requestAvatarGenerationPresenter as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, requestAvatarGenerationPresenter as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect( 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: '' }); ).resolves.toEqual({ success: true, requestId: 'r1', avatarUrls: ['u1'], errorMessage: '' });
expect(requestAvatarGenerationUseCase.execute).toHaveBeenCalledWith({ expect(requestAvatarGenerationUseCase.execute).toHaveBeenCalledWith({
userId: 'u1', userId: 'u1',
facePhotoData: {} as any, facePhotoData: {} as never,
suitColor: 'red', suitColor: 'red',
}); });
expect(requestAvatarGenerationPresenter.transform).toHaveBeenCalledWith({ requestId: 'r1', status: 'completed', avatarUrls: ['u1'] }); expect(requestAvatarGenerationPresenter.transform).toHaveBeenCalledWith({ requestId: 'r1', status: 'completed', avatarUrls: ['u1'] });
@@ -45,23 +47,25 @@ describe('MediaService', () => {
it('requestAvatarGeneration returns failure DTO on error', async () => { it('requestAvatarGeneration returns failure DTO on error', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'fail' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'fail' } })) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: { success: true } } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: { success: true } } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect( 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({ ).resolves.toEqual({
success: false, success: false,
requestId: '', requestId: '',
@@ -78,30 +82,32 @@ describe('MediaService', () => {
const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', url: 'https://example.com/m1.png' })) }; const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', url: 'https://example.com/m1.png' })) };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
uploadMediaUseCase as any, uploadMediaUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
uploadMediaPresenter as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, uploadMediaPresenter as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect( 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({ ).resolves.toEqual({
success: true, success: true,
mediaId: 'm1', mediaId: 'm1',
}); });
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({
file: {} as any, file: {} as never,
uploadedBy: 'u1', uploadedBy: 'u1',
metadata: { a: 1 }, metadata: { a: 1 },
}); });
@@ -116,23 +122,25 @@ describe('MediaService', () => {
}; };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
uploadMediaUseCase as any, uploadMediaUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
uploadMediaPresenter as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, uploadMediaPresenter as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.uploadMedia({ file: {} as any } as any)).resolves.toEqual({ success: true, mediaId: 'm1' }); await expect(service.uploadMedia({ file: {} as never } as never)).resolves.toEqual({ success: true, mediaId: 'm1' });
expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as any, uploadedBy: '', metadata: {} }); expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as never, uploadedBy: '', metadata: {} });
expect(uploadMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', url: 'https://example.com/m1.png' }); expect(uploadMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', url: 'https://example.com/m1.png' });
}); });
@@ -142,22 +150,24 @@ describe('MediaService', () => {
}; };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
uploadMediaUseCase as any, uploadMediaUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: { success: true } } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: { success: true } } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { 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, success: false,
error: 'nope', error: 'nope',
}); });
@@ -172,19 +182,21 @@ describe('MediaService', () => {
}; };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
getMediaUseCase as any, getMediaUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
getMediaPresenter as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, getMediaPresenter as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
const result = await service.getMedia('m1'); const result = await service.getMedia('m1');
@@ -195,19 +207,21 @@ describe('MediaService', () => {
it('getMedia returns null on MEDIA_NOT_FOUND', async () => { it('getMedia returns null on MEDIA_NOT_FOUND', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'MEDIA_NOT_FOUND', details: { message: 'n/a' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'MEDIA_NOT_FOUND', details: { message: 'n/a' } })) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.getMedia('m1')).resolves.toBeNull(); await expect(service.getMedia('m1')).resolves.toBeNull();
@@ -215,19 +229,21 @@ describe('MediaService', () => {
it('getMedia throws on non-not-found error', async () => { it('getMedia throws on non-not-found error', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.getMedia('m1')).rejects.toThrow('boom'); await expect(service.getMedia('m1')).rejects.toThrow('boom');
@@ -241,19 +257,21 @@ describe('MediaService', () => {
}; };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
deleteMediaUseCase as any, deleteMediaUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
deleteMediaPresenter as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, deleteMediaPresenter as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: true }); await expect(service.deleteMedia('m1')).resolves.toEqual({ success: true });
@@ -263,19 +281,21 @@ describe('MediaService', () => {
it('deleteMedia returns failure DTO on error', async () => { it('deleteMedia returns failure DTO on error', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: { success: true } } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: { success: true } } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.deleteMedia('m1')).resolves.toEqual({ success: false, error: 'nope' }); await expect(service.deleteMedia('m1')).resolves.toEqual({ success: false, error: 'nope' });
@@ -290,19 +310,21 @@ describe('MediaService', () => {
}; };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
getAvatarUseCase as any, getAvatarUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
getAvatarPresenter as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
getAvatarPresenter as never,
{ responseModel: {} } as never,
); );
await expect(service.getAvatar('d1')).resolves.toEqual({ avatarUrl: 'https://example.com/avatar.png' }); 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 () => { it('getAvatar returns null on AVATAR_NOT_FOUND', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'AVATAR_NOT_FOUND', details: { message: 'n/a' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'AVATAR_NOT_FOUND', details: { message: 'n/a' } })) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.getAvatar('d1')).resolves.toBeNull(); await expect(service.getAvatar('d1')).resolves.toBeNull();
@@ -332,19 +356,21 @@ describe('MediaService', () => {
it('getAvatar throws on non-not-found error', async () => { it('getAvatar throws on non-not-found error', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.getAvatar('d1')).rejects.toThrow('boom'); await expect(service.getAvatar('d1')).rejects.toThrow('boom');
@@ -358,44 +384,48 @@ describe('MediaService', () => {
}; };
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
updateAvatarUseCase as any, updateAvatarUseCase as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
updateAvatarPresenter as any, { 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(updateAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1', mediaUrl: 'u1' });
expect(updateAvatarPresenter.transform).toHaveBeenCalledWith({ avatarId: 'a1', driverId: 'd1' }); expect(updateAvatarPresenter.transform).toHaveBeenCalledWith({ avatarId: 'a1', driverId: 'd1' });
}); });
it('updateAvatar returns failure DTO on error', async () => { it('updateAvatar returns failure DTO on error', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: { success: true } } as any, { 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, success: false,
error: 'nope', error: 'nope',
}); });
@@ -403,23 +433,25 @@ describe('MediaService', () => {
it('requestAvatarGeneration uses fallback errorMessage when no details.message', async () => { it('requestAvatarGeneration uses fallback errorMessage when no details.message', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: { success: true } } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: { success: true } } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect( 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({ ).resolves.toEqual({
success: false, success: false,
requestId: '', requestId: '',
@@ -430,26 +462,28 @@ describe('MediaService', () => {
it('uploadMedia uses fallback error when no details.message', async () => { it('uploadMedia uses fallback error when no details.message', async () => {
const uploadMediaUseCase = { 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( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
uploadMediaUseCase as any, uploadMediaUseCase as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: { success: true } } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: { success: true } } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { 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, success: false,
error: 'Upload failed', error: 'Upload failed',
}); });
@@ -457,19 +491,21 @@ describe('MediaService', () => {
it('getMedia throws fallback message when no details.message and not not-found', async () => { it('getMedia throws fallback message when no details.message and not not-found', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.getMedia('m1')).rejects.toThrow('Failed to get media'); 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 () => { it('deleteMedia uses fallback message when no details.message', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: { success: true } } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { 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' }); 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 () => { it('getAvatar throws fallback message when no details.message and not not-found', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as never,
{ responseModel: {} } as never,
); );
await expect(service.getAvatar('d1')).rejects.toThrow('Failed to get avatar'); 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 () => { it('updateAvatar uses fallback message when no details.message', async () => {
const service = new MediaService( const service = new MediaService(
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn() } as any, { execute: vi.fn() } as never,
{ execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never,
logger as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, { execute: vi.fn() } as never,
{ responseModel: {} } as any, logger as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: {} } as any, { responseModel: {} } as never,
{ responseModel: { success: true } } as any, { 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, success: false,
error: 'Failed to update avatar', 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 { ValidateFaceOutputDTO } from './dtos/ValidateFaceOutputDTO';
import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest'; import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest';
import type { MulterFile } from './types/MulterFile'; import type { MulterFile } from './types/MulterFile';
import type { MediaReference } from '@core/domain/media/MediaReference';
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO; type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type UploadMediaInput = UploadMediaInputDTO; 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 { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase'; import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase'; 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) // Presenters (now transformers)
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter'; import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
@@ -40,6 +43,8 @@ import {
DELETE_MEDIA_USE_CASE_TOKEN, DELETE_MEDIA_USE_CASE_TOKEN,
GET_AVATAR_USE_CASE_TOKEN, GET_AVATAR_USE_CASE_TOKEN,
UPDATE_AVATAR_USE_CASE_TOKEN, UPDATE_AVATAR_USE_CASE_TOKEN,
RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
LOGGER_TOKEN, LOGGER_TOKEN,
} from './MediaTokens'; } from './MediaTokens';
import type { Logger } from '@core/shared/application'; import type { Logger } from '@core/shared/application';
@@ -59,6 +64,10 @@ export class MediaService {
private readonly getAvatarUseCase: GetAvatarUseCase, private readonly getAvatarUseCase: GetAvatarUseCase,
@Inject(UPDATE_AVATAR_USE_CASE_TOKEN) @Inject(UPDATE_AVATAR_USE_CASE_TOKEN)
private readonly updateAvatarUseCase: UpdateAvatarUseCase, 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) @Inject(LOGGER_TOKEN)
private readonly logger: Logger, private readonly logger: Logger,
private readonly requestAvatarGenerationPresenter: RequestAvatarGenerationPresenter, private readonly requestAvatarGenerationPresenter: RequestAvatarGenerationPresenter,
@@ -211,4 +220,20 @@ export class MediaService {
return { isValid: true }; 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 GET_MEDIA_USE_CASE_TOKEN = 'GetMediaUseCase';
export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase'; export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase'; 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 { NotificationsPersistenceModule } from '../../persistence/notifications/NotificationsPersistenceModule';
import { NotificationsController } from './NotificationsController'; import { NotificationsController } from './NotificationsController';
import { NotificationsService } from './NotificationsService'; import { NotificationsService } from './NotificationsService';
import { NotificationsProviders } from './NotificationsProviders';
@Module({ @Module({
imports: [NotificationsPersistenceModule], imports: [NotificationsPersistenceModule],
controllers: [NotificationsController], controllers: [NotificationsController],
providers: [NotificationsService], providers: [NotificationsService, ...NotificationsProviders],
exports: [NotificationsPersistenceModule, NotificationsService], 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 { Inject, Injectable } from '@nestjs/common';
import type { INotificationRepository } from '@core/notifications/domain/repositories/INotificationRepository'; import { GetUnreadNotificationsUseCase } from '@core/notifications/application/use-cases/GetUnreadNotificationsUseCase';
import { NOTIFICATION_REPOSITORY_TOKEN } from '../../persistence/notifications/NotificationsPersistenceTokens'; 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() @Injectable()
export class NotificationsService { export class NotificationsService {
constructor( constructor(
@Inject(NOTIFICATION_REPOSITORY_TOKEN) @Inject(GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN)
private readonly notificationRepository: INotificationRepository, 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>>> { async getUnreadNotifications(userId: string): Promise<Array<Record<string, unknown>>> {
const notifications = await this.notificationRepository.findUnreadByRecipientId(userId); const result = await this.getUnreadNotificationsUseCase.execute({ recipientId: userId });
return notifications.map(n => n.toJSON() as Record<string, unknown>); 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>>> { async getAllNotifications(userId: string): Promise<Array<Record<string, unknown>>> {
const notifications = await this.notificationRepository.findByRecipientId(userId); const result = await this.getAllNotificationsUseCase.execute({ recipientId: userId });
return notifications.map(n => n.toJSON() as Record<string, unknown>); 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> { async markAsRead(notificationId: string, userId: string): Promise<void> {
const notification = await this.notificationRepository.findById(notificationId); const result = await this.markNotificationReadUseCase.execute({
if (!notification) { notificationId,
throw new Error('Notification not found'); recipientId: userId,
} });
if (notification.recipientId !== userId) { if (result.isErr()) {
throw new Error('Unauthorized'); 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)', () => { describe('Payments domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();
@@ -62,9 +62,9 @@ describe('Payments domain (HTTP, module-wiring)', () => {
}; };
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -377,7 +377,7 @@ describe('PaymentsController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -415,9 +415,9 @@ describe('PaymentsController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -5,7 +5,9 @@ import { PaymentsService } from './PaymentsService';
describe('PaymentsService', () => { describe('PaymentsService', () => {
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }; 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 getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) };
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) }; const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) };
const updatePaymentStatusUseCase = const updatePaymentStatusUseCase =
@@ -25,19 +27,19 @@ describe('PaymentsService', () => {
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) }; overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
const service = new PaymentsService( const service = new PaymentsService(
getPaymentsUseCase as any, getPaymentsUseCase as never,
createPaymentUseCase as any, createPaymentUseCase as never,
updatePaymentStatusUseCase as any, updatePaymentStatusUseCase as never,
getMembershipFeesUseCase as any, getMembershipFeesUseCase as never,
upsertMembershipFeeUseCase as any, upsertMembershipFeeUseCase as never,
updateMemberPaymentUseCase as any, updateMemberPaymentUseCase as never,
getPrizesUseCase as any, getPrizesUseCase as never,
createPrizeUseCase as any, createPrizeUseCase as never,
awardPrizeUseCase as any, awardPrizeUseCase as never,
deletePrizeUseCase as any, deletePrizeUseCase as never,
getWalletUseCase as any, getWalletUseCase as never,
processWalletTransactionUseCase as any, processWalletTransactionUseCase as never,
logger as any, logger as never,
); );
return { return {
@@ -59,7 +61,7 @@ describe('PaymentsService', () => {
it('getPayments returns presenter model on success', async () => { it('getPayments returns presenter model on success', async () => {
const { service, getPaymentsUseCase } = makeService(); 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' }); expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
@@ -67,12 +69,12 @@ describe('PaymentsService', () => {
const { service } = makeService({ const { service } = makeService({
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, 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 () => { it('createPayment returns presenter model on success', async () => {
const { service, createPaymentUseCase } = makeService(); 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' }); expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
@@ -80,12 +82,12 @@ describe('PaymentsService', () => {
const { service } = makeService({ const { service } = makeService({
createPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, 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 () => { it('updatePaymentStatus returns presenter model on success', async () => {
const { service, updatePaymentStatusUseCase } = makeService(); 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' }); expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
}); });
@@ -93,7 +95,7 @@ describe('PaymentsService', () => {
const { service } = makeService({ const { service } = makeService({
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, 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 () => { it('getMembershipFees returns viewModel on success', async () => {
@@ -101,7 +103,7 @@ describe('PaymentsService', () => {
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) } 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 }, fee: { amount: 1 },
payments: [], payments: [],
}); });
@@ -112,22 +114,22 @@ describe('PaymentsService', () => {
const { service } = makeService({ const { service } = makeService({
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, 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 () => { it('upsertMembershipFee returns viewModel on success', async () => {
const { service, upsertMembershipFeeUseCase } = makeService(); 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' }); expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
it('upsertMembershipFee throws on error branch (defensive check)', async () => { it('upsertMembershipFee throws on error branch (defensive check)', async () => {
const { service } = makeService({ 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', 'Failed to upsert membership fee',
); );
}); });
@@ -135,7 +137,7 @@ describe('PaymentsService', () => {
it('updateMemberPayment returns viewModel on success', async () => { it('updateMemberPayment returns viewModel on success', async () => {
const { service, updateMemberPaymentUseCase } = makeService(); 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' }); expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
@@ -144,7 +146,7 @@ describe('PaymentsService', () => {
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, 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 () => { it('getPrizes maps seasonId optional', async () => {
@@ -153,10 +155,10 @@ describe('PaymentsService', () => {
getPrizesUseCase, 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' }); 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' }); expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 's1' });
}); });
@@ -166,7 +168,7 @@ describe('PaymentsService', () => {
createPrizeUseCase, 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' }); expect(createPrizeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
@@ -176,7 +178,7 @@ describe('PaymentsService', () => {
awardPrizeUseCase, 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' }); expect(awardPrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
}); });
@@ -186,7 +188,7 @@ describe('PaymentsService', () => {
deletePrizeUseCase, 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' }); expect(deletePrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
}); });
@@ -196,7 +198,7 @@ describe('PaymentsService', () => {
getWalletUseCase, 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' }); expect(getWalletUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
@@ -206,47 +208,47 @@ describe('PaymentsService', () => {
processWalletTransactionUseCase, 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' }); expect(processWalletTransactionUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
}); });
it('getPayments throws fallback message when error has no code', async () => { it('getPayments throws fallback message when error has no code', async () => {
const { service } = makeService({ 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 () => { it('createPayment throws fallback message when error has no code', async () => {
const { service } = makeService({ 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 () => { it('updatePaymentStatus throws fallback message when error has no code', async () => {
const { service } = makeService({ 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 () => { it('getMembershipFees throws fallback message when error has no code', async () => {
const { service } = makeService({ 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 () => { it('updateMemberPayment throws fallback message when error has no code', async () => {
const { service } = makeService({ 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)', () => { describe('Policy domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();
@@ -61,9 +61,9 @@ describe('Policy domain (HTTP, module-wiring)', () => {
}; };
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

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

View File

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

View File

@@ -58,8 +58,8 @@ describe('RaceController', () => {
describe('getAllRaces', () => { describe('getAllRaces', () => {
it('should return all races view model', async () => { it('should return all races view model', async () => {
const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as any; const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as unknown as { viewModel: unknown };
service.getAllRaces.mockResolvedValue(mockPresenter); service.getAllRaces.mockResolvedValue(mockPresenter as never);
const result = await controller.getAllRaces(); const result = await controller.getAllRaces();
@@ -70,8 +70,8 @@ describe('RaceController', () => {
describe('getTotalRaces', () => { describe('getTotalRaces', () => {
it('should return total races count view model', async () => { it('should return total races count view model', async () => {
const mockPresenter = { viewModel: { totalRaces: 5 } } as any; const mockPresenter = { viewModel: { totalRaces: 5 } } as unknown as { viewModel: unknown };
service.getTotalRaces.mockResolvedValue(mockPresenter); service.getTotalRaces.mockResolvedValue(mockPresenter as never);
const result = await controller.getTotalRaces(); const result = await controller.getTotalRaces();
@@ -81,7 +81,7 @@ describe('RaceController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -120,9 +120,9 @@ describe('RaceController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); 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 { RaceService } from './RaceService';
import { Result } from '@core/shared/application/Result'; import { Result } from '@core/shared/application/Result';
describe('RaceService', () => { describe('RaceService', () => {
it('invokes each use case and returns the corresponding presenter', async () => { it('invokes each use case and returns the corresponding presenter', async () => {
// Mock use cases to return Result.ok() // 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)) 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 logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
const getAllRacesPresenter = { present: vi.fn() } as any; const getAllRacesPresenter = { present: vi.fn() } as unknown as { present: Mock };
const getTotalRacesPresenter = { present: vi.fn() } as any; const getTotalRacesPresenter = { present: vi.fn() } as unknown as { present: Mock };
const importRaceResultsApiPresenter = { present: vi.fn() } as any; const importRaceResultsApiPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceDetailPresenter = { present: vi.fn() } as any; const raceDetailPresenter = { present: vi.fn() } as unknown as { present: Mock };
const racesPageDataPresenter = { present: vi.fn() } as any; const racesPageDataPresenter = { present: vi.fn() } as unknown as { present: Mock };
const allRacesPageDataPresenter = { present: vi.fn() } as any; const allRacesPageDataPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceResultsDetailPresenter = { present: vi.fn() } as any; const raceResultsDetailPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceWithSOFPresenter = { present: vi.fn() } as any; const raceWithSOFPresenter = { present: vi.fn() } as unknown as { present: Mock };
const raceProtestsPresenter = { present: vi.fn() } as any; const raceProtestsPresenter = { present: vi.fn() } as unknown as { present: Mock };
const racePenaltiesPresenter = { present: vi.fn() } as any; const racePenaltiesPresenter = { present: vi.fn() } as unknown as { present: Mock };
const commandResultPresenter = { present: vi.fn() } as any; const commandResultPresenter = { present: vi.fn() } as unknown as { present: Mock };
const service = new RaceService( const service = new RaceService(
getAllRacesUseCase as any, getAllRacesUseCase as never,
getTotalRacesUseCase as any, getTotalRacesUseCase as never,
importRaceResultsApiUseCase as any, importRaceResultsApiUseCase as never,
getRaceDetailUseCase as any, getRaceDetailUseCase as never,
getRacesPageDataUseCase as any, getRacesPageDataUseCase as never,
getAllRacesPageDataUseCase as any, getAllRacesPageDataUseCase as never,
getRaceResultsDetailUseCase as any, getRaceResultsDetailUseCase as never,
getRaceWithSOFUseCase as any, getRaceWithSOFUseCase as never,
getRaceProtestsUseCase as any, getRaceProtestsUseCase as never,
getRacePenaltiesUseCase as any, getRacePenaltiesUseCase as never,
registerForRaceUseCase as any, registerForRaceUseCase as never,
withdrawFromRaceUseCase as any, withdrawFromRaceUseCase as never,
cancelRaceUseCase as any, cancelRaceUseCase as never,
completeRaceUseCase as any, completeRaceUseCase as never,
fileProtestUseCase as any, fileProtestUseCase as never,
quickPenaltyUseCase as any, quickPenaltyUseCase as never,
applyPenaltyUseCase as any, applyPenaltyUseCase as never,
requestProtestDefenseUseCase as any, requestProtestDefenseUseCase as never,
reviewProtestUseCase as any, reviewProtestUseCase as never,
reopenRaceUseCase as any, reopenRaceUseCase as never,
logger as any, logger as never,
getAllRacesPresenter, getAllRacesPresenter as never,
getTotalRacesPresenter, getTotalRacesPresenter as never,
importRaceResultsApiPresenter, importRaceResultsApiPresenter as never,
raceDetailPresenter, raceDetailPresenter as never,
racesPageDataPresenter, racesPageDataPresenter as never,
allRacesPageDataPresenter, allRacesPageDataPresenter as never,
raceResultsDetailPresenter, raceResultsDetailPresenter as never,
raceWithSOFPresenter, raceWithSOFPresenter as never,
raceProtestsPresenter, raceProtestsPresenter as never,
racePenaltiesPresenter, racePenaltiesPresenter as never,
commandResultPresenter, commandResultPresenter as never,
); );
expect(await service.getAllRaces()).toBe(getAllRacesPresenter); expect(await service.getAllRaces()).toBe(getAllRacesPresenter);
@@ -87,11 +87,11 @@ describe('RaceService', () => {
expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({}); expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({});
expect(getTotalRacesPresenter.present).toHaveBeenCalledWith({ totalRaces: 0 }); 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(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' });
expect(importRaceResultsApiPresenter.present).toHaveBeenCalledWith({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] }); 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(getRaceDetailUseCase.execute).toHaveBeenCalled();
expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter); expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter);
@@ -118,43 +118,43 @@ describe('RaceService', () => {
expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' }); expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(racePenaltiesPresenter.present).toHaveBeenCalledWith({ penalties: [], drivers: [] }); 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(registerForRaceUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).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(withdrawFromRaceUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).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(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' });
expect(commandResultPresenter.present).toHaveBeenCalled(); 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(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' });
expect(commandResultPresenter.present).toHaveBeenCalled(); 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(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' });
expect(commandResultPresenter.present).toHaveBeenCalled(); 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(fileProtestUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).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(quickPenaltyUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).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(applyPenaltyUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).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(requestProtestDefenseUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).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(reviewProtestUseCase.execute).toHaveBeenCalled();
expect(commandResultPresenter.present).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled();
}); });

View File

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

View File

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

View File

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

View File

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

View File

@@ -53,7 +53,7 @@ describe('SponsorController', () => {
describe('getEntitySponsorshipPricing', () => { describe('getEntitySponsorshipPricing', () => {
it('should return sponsorship pricing', async () => { it('should return sponsorship pricing', async () => {
const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] }; 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(); const result = await controller.getEntitySponsorshipPricing();
@@ -65,7 +65,7 @@ describe('SponsorController', () => {
describe('getSponsors', () => { describe('getSponsors', () => {
it('should return sponsors list', async () => { it('should return sponsors list', async () => {
const mockResult = { sponsors: [] }; const mockResult = { sponsors: [] };
sponsorService.getSponsors.mockResolvedValue(mockResult as any); sponsorService.getSponsors.mockResolvedValue(mockResult as never);
const result = await controller.getSponsors(); const result = await controller.getSponsors();
@@ -78,9 +78,9 @@ describe('SponsorController', () => {
it('should create sponsor', async () => { it('should create sponsor', async () => {
const input = { name: 'Test Sponsor', contactEmail: 'test@example.com' }; const input = { name: 'Test Sponsor', contactEmail: 'test@example.com' };
const mockResult = { sponsor: { id: 's1', name: 'Test Sponsor' } }; 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(result).toEqual(mockResult);
expect(sponsorService.createSponsor).toHaveBeenCalledWith(input); expect(sponsorService.createSponsor).toHaveBeenCalledWith(input);
@@ -90,8 +90,8 @@ describe('SponsorController', () => {
describe('getSponsorDashboard', () => { describe('getSponsorDashboard', () => {
it('should return sponsor dashboard', async () => { it('should return sponsor dashboard', async () => {
const sponsorId = 's1'; const sponsorId = 's1';
const mockResult = { sponsorId, metrics: {} as any, sponsoredLeagues: [], investment: {} as any }; const mockResult = { sponsorId, metrics: {} as never, sponsoredLeagues: [], investment: {} as never };
sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as any); sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as never);
const result = await controller.getSponsorDashboard(sponsorId); const result = await controller.getSponsorDashboard(sponsorId);
@@ -122,7 +122,7 @@ describe('SponsorController', () => {
currency: 'USD', currency: 'USD',
}, },
}; };
sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as any); sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as never);
const result = await controller.getSponsorSponsorships(sponsorId); const result = await controller.getSponsorSponsorships(sponsorId);
@@ -142,7 +142,7 @@ describe('SponsorController', () => {
it('should return sponsor', async () => { it('should return sponsor', async () => {
const sponsorId = 's1'; const sponsorId = 's1';
const mockResult = { sponsor: { id: sponsorId, name: '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); const result = await controller.getSponsor(sponsorId);
@@ -167,7 +167,7 @@ describe('SponsorController', () => {
requests: [], requests: [],
totalCount: 0, totalCount: 0,
}; };
sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as any); sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as never);
const result = await controller.getPendingSponsorshipRequests(query); const result = await controller.getPendingSponsorshipRequests(query);
@@ -188,9 +188,9 @@ describe('SponsorController', () => {
platformFee: 10, platformFee: 10,
netAmount: 90, 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(result).toEqual(mockResult);
expect(sponsorService.acceptSponsorshipRequest).toHaveBeenCalledWith( expect(sponsorService.acceptSponsorshipRequest).toHaveBeenCalledWith(
@@ -204,7 +204,7 @@ describe('SponsorController', () => {
const input = { respondedBy: 'u1' }; const input = { respondedBy: 'u1' };
sponsorService.acceptSponsorshipRequest.mockRejectedValue(new Error('Accept sponsorship request failed')); 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(), rejectedAt: new Date(),
reason: 'Not interested', 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(result).toEqual(mockResult);
expect(sponsorService.rejectSponsorshipRequest).toHaveBeenCalledWith( expect(sponsorService.rejectSponsorshipRequest).toHaveBeenCalledWith(
@@ -235,7 +235,7 @@ describe('SponsorController', () => {
const input = { respondedBy: 'u1' }; const input = { respondedBy: 'u1' };
sponsorService.rejectSponsorshipRequest.mockRejectedValue(new Error('Reject sponsorship request failed')); 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, averageMonthlySpend: 0,
}, },
}; };
sponsorService.getSponsorBilling.mockResolvedValue(mockResult as any); sponsorService.getSponsorBilling.mockResolvedValue(mockResult as never);
const result = await controller.getSponsorBilling(sponsorId); const result = await controller.getSponsorBilling(sponsorId);
@@ -265,8 +265,8 @@ describe('SponsorController', () => {
describe('getAvailableLeagues', () => { describe('getAvailableLeagues', () => {
it('should return available leagues', async () => { it('should return available leagues', async () => {
const mockResult: any[] = []; const mockResult: unknown[] = [];
sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as any); sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.getAvailableLeagues(); const result = await controller.getAvailableLeagues();
@@ -279,11 +279,11 @@ describe('SponsorController', () => {
it('should return league detail', async () => { it('should return league detail', async () => {
const leagueId = 'league-1'; const leagueId = 'league-1';
const mockResult = { const mockResult = {
league: { id: leagueId } as any, league: { id: leagueId } as never,
drivers: [], drivers: [],
races: [], races: [],
}; };
sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as any); sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.getLeagueDetail(leagueId); const result = await controller.getLeagueDetail(leagueId);
@@ -296,11 +296,11 @@ describe('SponsorController', () => {
it('should return sponsor settings', async () => { it('should return sponsor settings', async () => {
const sponsorId = 's1'; const sponsorId = 's1';
const mockResult = { const mockResult = {
profile: {} as any, profile: {} as never,
notifications: {} as any, notifications: {} as never,
privacy: {} as any, privacy: {} as never,
}; };
sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any); sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as never);
const result = await controller.getSponsorSettings(sponsorId); const result = await controller.getSponsorSettings(sponsorId);
@@ -314,7 +314,7 @@ describe('SponsorController', () => {
const sponsorId = 's1'; const sponsorId = 's1';
const input = {}; const input = {};
const mockResult = { success: true }; 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); 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 () => { it('throws using error.message when details.message is missing', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' }; 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'); await expect(service.createSponsor(input)).rejects.toThrow('Boom');
}); });
it('throws default message when details.message and message are missing', async () => { it('throws default message when details.message and message are missing', async () => {
const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' }; 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'); await expect(service.createSponsor(input)).rejects.toThrow('Failed to create sponsor');
}); });
@@ -476,7 +476,7 @@ describe('SponsorService', () => {
}); });
it('throws on error', async () => { 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'); 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)', () => { describe('Team domain (HTTP, module-wiring)', () => {
const originalEnv = { ...process.env }; const originalEnv = { ...process.env };
let app: any; let app: import("@nestjs/common").INestApplication;
beforeAll(async () => { beforeAll(async () => {
vi.resetModules(); vi.resetModules();
@@ -62,9 +62,9 @@ describe('Team domain (HTTP, module-wiring)', () => {
}; };
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); 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 }; const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false };
service.getDetails.mockResolvedValue(result); service.getDetails.mockResolvedValue(result);
const mockReq = { user: { userId } } as any; const mockReq = { user: { userId } } as never;
const response = await controller.getDetails(teamId, mockReq); const response = await controller.getDetails(teamId, mockReq);
@@ -103,7 +103,7 @@ describe('TeamController', () => {
const result = { id: 'team-456', success: true }; const result = { id: 'team-456', success: true };
service.create.mockResolvedValue(result); service.create.mockResolvedValue(result);
const mockReq = { user: { userId } } as any; const mockReq = { user: { userId } } as never;
const response = await controller.create(input, mockReq); const response = await controller.create(input, mockReq);
@@ -120,7 +120,7 @@ describe('TeamController', () => {
const result = { success: true }; const result = { success: true };
service.update.mockResolvedValue(result); service.update.mockResolvedValue(result);
const mockReq = { user: { userId } } as any; const mockReq = { user: { userId } } as never;
const response = await controller.update(teamId, input, mockReq); const response = await controller.update(teamId, input, mockReq);
@@ -157,7 +157,7 @@ describe('TeamController', () => {
}); });
describe('auth guards (HTTP)', () => { describe('auth guards (HTTP)', () => {
let app: any; let app: import("@nestjs/common").INestApplication;
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = { const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
getCurrentSession: vi.fn(async () => null), getCurrentSession: vi.fn(async () => null),
@@ -196,9 +196,9 @@ describe('TeamController', () => {
const reflector = new Reflector(); const reflector = new Reflector();
app.useGlobalGuards( app.useGlobalGuards(
new AuthenticationGuard(sessionPort as any), new AuthenticationGuard(sessionPort as never),
new AuthorizationGuard(reflector, authorizationService as any), new AuthorizationGuard(reflector, authorizationService as never),
new FeatureAvailabilityGuard(reflector, policyService as any), new FeatureAvailabilityGuard(reflector, policyService as never),
); );
await app.init(); await app.init();

View File

@@ -1,6 +1,24 @@
import { Provider } from '@nestjs/common'; 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 { export {
TEAM_REPOSITORY_TOKEN, TEAM_REPOSITORY_TOKEN,
@@ -15,6 +33,10 @@ export {
// Import core interfaces // Import core interfaces
import type { Logger } from '@core/shared/application/Logger'; import type { Logger } from '@core/shared/application/Logger';
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort'; 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 concrete implementations
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter'; 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 { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter'; 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 presenters
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter'; import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
@@ -69,4 +102,59 @@ export const TeamProviders: Provider[] = [
}, },
inject: [MEDIA_RESOLVER_TOKEN], 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; ownerId: ValueObjectStub;
leagues: ValueObjectStub[]; leagues: ValueObjectStub[];
createdAt: { toDate(): Date }; createdAt: { toDate(): Date };
logoRef: any; logoRef: unknown;
category: string | undefined; category: string | undefined;
isRecruiting: boolean; isRecruiting: boolean;
update: Mock; update: Mock;
@@ -124,13 +124,17 @@ describe('TeamService', () => {
clear: vi.fn(), clear: vi.fn(),
}; };
service = new TeamService( service = new TeamService(
teamRepository as unknown as never, new GetAllTeamsUseCase(teamRepository as never, membershipRepository as never, teamStatsRepository as never, logger),
membershipRepository as unknown as never, new GetTeamDetailsUseCase(teamRepository as never, membershipRepository as never),
driverRepository as unknown as never, new GetTeamMembersUseCase(membershipRepository as never, driverRepository as never, teamRepository as never, logger),
logger, new GetTeamJoinRequestsUseCase(membershipRepository as never, driverRepository as never, teamRepository as never),
teamStatsRepository as unknown 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 // Core imports
import type { Logger } from '@core/shared/application/Logger'; 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 // Use cases
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; 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'; import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
// Tokens // Tokens
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN } from './TeamTokens'; import {
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository'; 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() @Injectable()
export class TeamService { export class TeamService {
constructor( constructor(
@Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository, @Inject(GET_ALL_TEAMS_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
@Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository, @Inject(GET_TEAM_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase,
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository, @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(LOGGER_TOKEN) private readonly logger: Logger,
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
) {} ) {}
async getAll(): Promise<GetAllTeamsOutputDTO> { async getAll(): Promise<GetAllTeamsOutputDTO> {
this.logger.debug('[TeamService] Fetching all teams.'); this.logger.debug('[TeamService] Fetching all teams.');
const useCase = new GetAllTeamsUseCase( const result = await this.getAllTeamsUseCase.execute({});
this.teamRepository,
this.membershipRepository,
this.teamStatsRepository,
this.logger
);
const result = await useCase.execute({});
if (result.isErr()) { if (result.isErr()) {
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error')); this.logger.error('Error fetching all teams', new Error(result.unwrapErr().details?.message || 'Unknown error'));
return { teams: [], totalCount: 0 };
}
const value = result.value;
if (!value) {
return { teams: [], totalCount: 0 }; return { teams: [], totalCount: 0 };
} }
const value = result.unwrap();
return { return {
teams: value.teams.map(t => ({ teams: value.teams.map(t => ({
id: t.team.id, id: t.team.id,
@@ -86,18 +88,13 @@ export class TeamService {
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> { async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`); this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository); const result = await this.getTeamDetailsUseCase.execute({ teamId, driverId: userId || '' });
const result = await useCase.execute({ teamId, driverId: userId || '' });
if (result.isErr()) { if (result.isErr()) {
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return null;
}
const value = result.value;
if (!value) {
return null; return null;
} }
const value = result.unwrap();
// Convert to DTO // Convert to DTO
return { return {
team: { team: {
@@ -123,21 +120,9 @@ export class TeamService {
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> { async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`); 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 this.getTeamMembersUseCase.execute({ teamId });
const result = await useCase.execute({ teamId });
if (result.isErr()) { if (result.isErr()) {
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return {
members: [],
totalCount: 0,
ownerCount: 0,
managerCount: 0,
memberCount: 0,
};
}
const value = result.value;
if (!value) {
return { return {
members: [], members: [],
totalCount: 0, totalCount: 0,
@@ -147,6 +132,7 @@ export class TeamService {
}; };
} }
const value = result.unwrap();
return { return {
members: value.members.map(m => ({ members: value.members.map(m => ({
driverId: m.driver?.id || '', driverId: m.driver?.id || '',
@@ -166,19 +152,9 @@ export class TeamService {
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> { async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`); this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository); const result = await this.getTeamJoinRequestsUseCase.execute({ teamId });
const result = await useCase.execute({ teamId });
if (result.isErr()) { if (result.isErr()) {
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error')); this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.unwrapErr().details?.message || 'Unknown error'));
return {
requests: [],
pendingCount: 0,
totalCount: 0,
};
}
const value = result.value;
if (!value) {
return { return {
requests: [], requests: [],
pendingCount: 0, pendingCount: 0,
@@ -186,6 +162,7 @@ export class TeamService {
}; };
} }
const value = result.unwrap();
return { return {
requests: value.joinRequests.map(r => ({ requests: value.joinRequests.map(r => ({
requestId: r.id, requestId: r.id,
@@ -212,18 +189,13 @@ export class TeamService {
leagues: [], leagues: [],
}; };
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger); const result = await this.createTeamUseCase.execute(command);
const result = await useCase.execute(command);
if (result.isErr()) { if (result.isErr()) {
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`); this.logger.error(`Error creating team: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return { id: '', success: false };
}
const value = result.value;
if (!value) {
return { id: '', success: false }; return { id: '', success: false };
} }
const value = result.unwrap();
return { id: value.team.id, success: true }; return { id: value.team.id, success: true };
} }
@@ -240,10 +212,9 @@ export class TeamService {
updatedBy: userId || '', updatedBy: userId || '',
}; };
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository); const result = await this.updateTeamUseCase.execute(command);
const result = await useCase.execute(command);
if (result.isErr()) { 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 }; return { success: false };
} }
@@ -253,14 +224,13 @@ export class TeamService {
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> { async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`); this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger); const result = await this.getDriverTeamUseCase.execute({ driverId });
const result = await useCase.execute({ driverId });
if (result.isErr()) { 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; return null;
} }
const value = result.value; const value = result.unwrap();
if (!value || !value.team) { if (!value || !value.team) {
return null; return null;
} }
@@ -290,18 +260,13 @@ export class TeamService {
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> { async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`); this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger); const result = await this.getTeamMembershipUseCase.execute({ teamId, driverId });
const result = await useCase.execute({ teamId, driverId });
if (result.isErr()) { if (result.isErr()) {
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`); this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
return null;
}
const value = result.value;
if (!value) {
return null; return null;
} }
const value = result.unwrap();
return value.membership ? { return value.membership ? {
role: value.membership.role, role: value.membership.role,
joinedAt: value.membership.joinedAt, joinedAt: value.membership.joinedAt,
@@ -316,10 +281,9 @@ export class TeamService {
throw new Error('User ID is required'); throw new Error('User ID is required');
} }
const useCase = new JoinTeamUseCase(this.teamRepository, this.membershipRepository, this.logger); const result = await this.joinTeamUseCase.execute({ teamId, driverId: userId });
const result = await useCase.execute({ teamId, driverId: userId });
if (result.isErr()) { if (result.isErr()) {
const error = result.error; const error = result.unwrapErr();
this.logger.error(`Error joining team ${teamId}: ${error?.details?.message || 'Unknown error'}`); this.logger.error(`Error joining team ${teamId}: ${error?.details?.message || 'Unknown error'}`);
return { success: false, error: 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 TEAM_STATS_REPOSITORY_TOKEN = 'ITeamStatsRepository';
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository'; export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort'; 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 = { const result = {
team: { team: {
id: 'team-123', id: 'team-123',
} as any, } as never,
}; };
presenter.present(result); presenter.present(result);
@@ -27,7 +27,7 @@ describe('CreateTeamPresenter', () => {
const result = { const result = {
team: { team: {
id: 'team-456', id: 'team-456',
} as any, } as never,
}; };
presenter.present(result); presenter.present(result);
@@ -41,7 +41,7 @@ describe('CreateTeamPresenter', () => {
describe('reset', () => { describe('reset', () => {
it('should clear the model', () => { 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(); expect(presenter.responseModel).toBeDefined();
presenter.reset(); presenter.reset();
@@ -55,7 +55,7 @@ describe('CreateTeamPresenter', () => {
}); });
it('should return model after present()', () => { 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({ expect(presenter.getResponseModel()).toEqual({
id: 'team-123', id: 'team-123',
success: true, success: true,
@@ -69,7 +69,7 @@ describe('CreateTeamPresenter', () => {
}); });
it('should return model after present()', () => { 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({ expect(presenter.responseModel).toEqual({
id: 'team-123', id: 'team-123',
success: true, success: true,

View File

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

View File

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