From 20a42c52fd3665e09ee0bb6abf0f006420df315b Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Fri, 16 Jan 2026 12:55:48 +0100 Subject: [PATCH] website refactor --- apps/api/src/domain/admin/Admin.http.test.ts | 10 +- .../src/domain/admin/AdminController.test.ts | 14 +- .../api/src/domain/admin/AdminService.test.ts | 4 +- .../admin/dtos/ListUsersRequestDto.test.ts | 4 +- .../domain/analytics/Analytics.http.test.ts | 8 +- .../analytics/AnalyticsController.test.ts | 10 +- .../domain/analytics/AnalyticsService.test.ts | 116 ++-- .../src/domain/auth/ActorFromSession.test.ts | 20 +- .../src/domain/auth/AuthController.test.ts | 12 +- .../src/domain/auth/AuthGuards.http.test.ts | 8 +- apps/api/src/domain/auth/AuthService.test.ts | 234 +++---- .../src/domain/auth/AuthSession.http.test.ts | 2 +- .../domain/auth/AuthenticationGuard.test.ts | 24 +- .../domain/auth/AuthorizationGuard.test.ts | 20 +- .../BootstrapModule.postgres-seed.test.ts | 8 +- .../domain/bootstrap/BootstrapModule.test.ts | 8 +- .../bootstrap/BootstrapSeed.http.test.ts | 2 +- .../domain/dashboard/Dashboard.http.test.ts | 8 +- .../dashboard/DashboardController.test.ts | 8 +- .../domain/dashboard/DashboardService.test.ts | 18 +- .../api/src/domain/driver/Driver.http.test.ts | 8 +- .../domain/driver/DriverController.test.ts | 8 +- apps/api/src/domain/driver/DriverProviders.ts | 7 + .../src/domain/driver/DriverService.test.ts | 57 +- apps/api/src/domain/driver/DriverService.ts | 30 +- apps/api/src/domain/driver/DriverTokens.ts | 1 + .../CompleteOnboardingPresenter.test.ts | 6 +- apps/api/src/domain/hello/Hello.http.test.ts | 8 +- .../domain/league/LeagueController.test.ts | 14 +- .../league/LeagueRosterAdminRead.http.test.ts | 20 +- ...eRosterJoinRequests.mutations.http.test.ts | 27 +- ...LeagueRosterMembers.mutations.http.test.ts | 23 +- .../league/LeagueScheduleAdmin.http.test.ts | 14 +- .../league/LeagueSchedulePublish.http.test.ts | 8 +- .../src/domain/league/LeagueService.test.ts | 194 +++--- ...uesWithCapacityAndScoringPresenter.test.ts | 10 +- .../presenters/CreateLeaguePresenter.test.ts | 8 +- .../GetLeagueMembershipsPresenter.test.ts | 6 +- .../presenters/LeagueConfigPresenter.test.ts | 28 +- .../presenters/LeagueConfigPresenter.ts | 28 +- .../LeagueJoinRequestsPresenter.test.ts | 2 +- .../LeagueOwnerSummaryPresenter.test.ts | 6 +- .../LeagueSchedulePresenter.test.ts | 10 +- .../media/DefaultAvatarAssets.http.test.ts | 2 +- .../src/domain/media/MediaController.test.ts | 22 +- apps/api/src/domain/media/MediaController.ts | 26 +- apps/api/src/domain/media/MediaProviders.ts | 22 +- .../api/src/domain/media/MediaService.test.ts | 618 ++++++++++-------- apps/api/src/domain/media/MediaService.ts | 27 +- apps/api/src/domain/media/MediaTokens.ts | 5 +- .../notifications/NotificationsModule.ts | 5 +- .../notifications/NotificationsProviders.ts | 36 + .../notifications/NotificationsService.ts | 47 +- .../notifications/NotificationsTokens.ts | 4 + .../src/domain/payments/Payments.http.test.ts | 8 +- .../payments/PaymentsController.test.ts | 8 +- .../domain/payments/PaymentsService.test.ts | 90 +-- .../src/domain/payments/presenters/index.ts | 12 - .../api/src/domain/policy/Policy.http.test.ts | 8 +- .../src/domain/protests/Protests.http.test.ts | 8 +- apps/api/src/domain/race/Race.http.test.ts | 8 +- .../src/domain/race/RaceController.test.ts | 16 +- apps/api/src/domain/race/RaceService.test.ts | 114 ++-- .../presenters/GetAllRacesPresenter.test.ts | 8 +- .../presenters/RacePenaltiesPresenter.test.ts | 26 +- .../presenters/RaceProtestsPresenter.test.ts | 68 +- .../src/domain/sponsor/Sponsor.http.test.ts | 8 +- .../domain/sponsor/SponsorController.test.ts | 50 +- .../src/domain/sponsor/SponsorService.test.ts | 6 +- apps/api/src/domain/team/Team.http.test.ts | 8 +- .../src/domain/team/TeamController.test.ts | 14 +- apps/api/src/domain/team/TeamProviders.ts | 92 ++- apps/api/src/domain/team/TeamService.test.ts | 18 +- apps/api/src/domain/team/TeamService.ts | 128 ++-- apps/api/src/domain/team/TeamTokens.ts | 12 +- .../presenters/CreateTeamPresenter.test.ts | 10 +- .../presenters/DriverTeamPresenter.test.ts | 112 ++-- apps/api/src/env.ts | 2 +- apps/api/src/features/features.http.test.ts | 2 +- .../use-cases/GetUploadedMediaUseCase.ts | 34 + .../use-cases/ResolveMediaReferenceUseCase.ts | 20 + .../use-cases/GetAllNotificationsUseCase.ts | 63 ++ .../application/use-cases/GetDriverUseCase.ts | 20 + 83 files changed, 1610 insertions(+), 1238 deletions(-) create mode 100644 apps/api/src/domain/notifications/NotificationsProviders.ts create mode 100644 apps/api/src/domain/notifications/NotificationsTokens.ts delete mode 100644 apps/api/src/domain/payments/presenters/index.ts create mode 100644 core/media/application/use-cases/GetUploadedMediaUseCase.ts create mode 100644 core/media/application/use-cases/ResolveMediaReferenceUseCase.ts create mode 100644 core/notifications/application/use-cases/GetAllNotificationsUseCase.ts create mode 100644 core/racing/application/use-cases/GetDriverUseCase.ts diff --git a/apps/api/src/domain/admin/Admin.http.test.ts b/apps/api/src/domain/admin/Admin.http.test.ts index b325f9533..8049010c3 100644 --- a/apps/api/src/domain/admin/Admin.http.test.ts +++ b/apps/api/src/domain/admin/Admin.http.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; -import { ValidationPipe } from '@nestjs/common'; +import { ValidationPipe, INestApplication } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import request from 'supertest'; @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Admin domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Admin domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/admin/AdminController.test.ts b/apps/api/src/domain/admin/AdminController.test.ts index f1e958d13..dadf8abc0 100644 --- a/apps/api/src/domain/admin/AdminController.test.ts +++ b/apps/api/src/domain/admin/AdminController.test.ts @@ -48,7 +48,7 @@ describe('AdminController', () => { limit: 10, }; - const req = { user: { userId: 'admin-1' } } as any; + const req = { user: { userId: 'admin-1' } } as never; const result = await controller.listUsers(query, req); expect(mockService.listUsers).toHaveBeenCalledWith({ @@ -81,7 +81,7 @@ describe('AdminController', () => { sortDirection: 'desc', }; - const req = { user: { userId: 'owner-1' } } as any; + const req = { user: { userId: 'owner-1' } } as never; const result = await controller.listUsers(query, req); expect(mockService.listUsers).toHaveBeenCalledWith({ @@ -110,7 +110,7 @@ describe('AdminController', () => { mockService.listUsers.mockResolvedValue(mockResponse); const query: ListUsersRequestDto = { page: 1, limit: 10 }; - const req = {} as any; + const req = {} as never; await controller.listUsers(query, req); @@ -137,7 +137,7 @@ describe('AdminController', () => { limit: 10, }; - const req = { user: { userId: 'admin-1' } } as any; + const req = { user: { userId: 'admin-1' } } as never; await controller.listUsers(query, req); expect(mockService.listUsers).toHaveBeenCalledWith({ @@ -170,7 +170,7 @@ describe('AdminController', () => { mockService.getDashboardStats.mockResolvedValue(mockStats); - const req = { user: { userId: 'admin-1' } } as any; + const req = { user: { userId: 'admin-1' } } as never; const result = await controller.getDashboardStats(req); expect(mockService.getDashboardStats).toHaveBeenCalledWith({ @@ -200,7 +200,7 @@ describe('AdminController', () => { mockService.getDashboardStats.mockResolvedValue(mockStats); - const req = {} as any; + const req = {} as never; const result = await controller.getDashboardStats(req); expect(mockService.getDashboardStats).toHaveBeenCalledWith({ @@ -212,7 +212,7 @@ describe('AdminController', () => { it('should handle service errors gracefully', async () => { mockService.getDashboardStats.mockRejectedValue(new Error('Database connection failed')); - const req = { user: { userId: 'admin-1' } } as any; + const req = { user: { userId: 'admin-1' } } as never; await expect(controller.getDashboardStats(req)).rejects.toThrow('Database connection failed'); }); diff --git a/apps/api/src/domain/admin/AdminService.test.ts b/apps/api/src/domain/admin/AdminService.test.ts index b363aaea4..57595b53d 100644 --- a/apps/api/src/domain/admin/AdminService.test.ts +++ b/apps/api/src/domain/admin/AdminService.test.ts @@ -19,8 +19,8 @@ describe('AdminService', () => { beforeEach(() => { vi.clearAllMocks(); service = new AdminService( - mockListUsersUseCase as any, - mockGetDashboardStatsUseCase as any + mockListUsersUseCase as never, + mockGetDashboardStatsUseCase as never ); }); diff --git a/apps/api/src/domain/admin/dtos/ListUsersRequestDto.test.ts b/apps/api/src/domain/admin/dtos/ListUsersRequestDto.test.ts index f42f58788..9170d189e 100644 --- a/apps/api/src/domain/admin/dtos/ListUsersRequestDto.test.ts +++ b/apps/api/src/domain/admin/dtos/ListUsersRequestDto.test.ts @@ -79,8 +79,8 @@ describe('ListUsersRequestDto', () => { it('should handle numeric string values for pagination', () => { // Arrange & Act const dto = new ListUsersRequestDto(); - dto.page = '5' as any; - dto.limit = '25' as any; + dto.page = '5' as never; + dto.limit = '25' as never; // Assert - Should accept the values expect(dto.page).toBe('5'); diff --git a/apps/api/src/domain/analytics/Analytics.http.test.ts b/apps/api/src/domain/analytics/Analytics.http.test.ts index 9b7f412c3..56a5d4bcf 100644 --- a/apps/api/src/domain/analytics/Analytics.http.test.ts +++ b/apps/api/src/domain/analytics/Analytics.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Analytics domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Analytics domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/analytics/AnalyticsController.test.ts b/apps/api/src/domain/analytics/AnalyticsController.test.ts index 81146d303..0fb545c2d 100644 --- a/apps/api/src/domain/analytics/AnalyticsController.test.ts +++ b/apps/api/src/domain/analytics/AnalyticsController.test.ts @@ -27,7 +27,7 @@ describe('AnalyticsController', () => { recordEngagement: vi.fn(), getDashboardData: vi.fn(), getAnalyticsMetrics: vi.fn(), - } as any; + } as never; controller = new AnalyticsController(service); }); @@ -121,7 +121,7 @@ describe('AnalyticsController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -168,9 +168,9 @@ describe('AnalyticsController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/analytics/AnalyticsService.test.ts b/apps/api/src/domain/analytics/AnalyticsService.test.ts index db4730750..eee2e7998 100644 --- a/apps/api/src/domain/analytics/AnalyticsService.test.ts +++ b/apps/api/src/domain/analytics/AnalyticsService.test.ts @@ -17,10 +17,10 @@ describe('AnalyticsService', () => { }; const service = new AnalyticsService( - recordPageViewUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, + recordPageViewUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, recordPageViewPresenter, new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -28,9 +28,9 @@ describe('AnalyticsService', () => { ); const dto = await service.recordPageView({ - entityType: 'league' as any, + entityType: 'league' as never, entityId: 'l1', - visitorType: 'anonymous' as any, + visitorType: 'anonymous' as never, sessionId: 's1', }); @@ -40,10 +40,10 @@ describe('AnalyticsService', () => { it('recordPageView throws on use case error', async () => { const service = new AnalyticsService( - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -51,16 +51,16 @@ describe('AnalyticsService', () => { ); await expect( - service.recordPageView({ entityType: 'league' as any, entityId: 'l1', visitorType: 'anonymous' as any, sessionId: 's1' }), + service.recordPageView({ entityType: 'league' as never, entityId: 'l1', visitorType: 'anonymous' as never, sessionId: 's1' }), ).rejects.toThrow('nope'); }); it('recordPageView throws with fallback message when no details.message', async () => { const service = new AnalyticsService( - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -68,7 +68,7 @@ describe('AnalyticsService', () => { ); await expect( - service.recordPageView({ entityType: 'league' as any, entityId: 'l1', visitorType: 'anonymous' as any, sessionId: 's1' }), + service.recordPageView({ entityType: 'league' as never, entityId: 'l1', visitorType: 'anonymous' as never, sessionId: 's1' }), ).rejects.toThrow('Failed to record page view'); }); @@ -81,10 +81,10 @@ describe('AnalyticsService', () => { }; const service = new AnalyticsService( - { execute: vi.fn() } as any, - recordEngagementUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, + { execute: vi.fn() } as never, + recordEngagementUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), recordEngagementPresenter, new GetDashboardDataPresenter(), @@ -92,8 +92,8 @@ describe('AnalyticsService', () => { ); const dto = await service.recordEngagement({ - action: 'click' as any, - entityType: 'league' as any, + action: 'click' as never, + entityType: 'league' as never, entityId: 'l1', actorType: 'anonymous', sessionId: 's1', @@ -104,10 +104,10 @@ describe('AnalyticsService', () => { it('recordEngagement throws with details message on error', async () => { const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -116,8 +116,8 @@ describe('AnalyticsService', () => { await expect( service.recordEngagement({ - action: 'click' as any, - entityType: 'league' as any, + action: 'click' as never, + entityType: 'league' as never, entityId: 'l1', actorType: 'anonymous', sessionId: 's1', @@ -127,10 +127,10 @@ describe('AnalyticsService', () => { it('recordEngagement throws with fallback message when no details.message', async () => { const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -139,8 +139,8 @@ describe('AnalyticsService', () => { await expect( service.recordEngagement({ - action: 'click' as any, - entityType: 'league' as any, + action: 'click' as never, + entityType: 'league' as never, entityId: 'l1', actorType: 'anonymous', sessionId: 's1', @@ -162,10 +162,10 @@ describe('AnalyticsService', () => { }; const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - getDashboardDataUseCase as any, - { execute: vi.fn() } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + getDashboardDataUseCase as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), getDashboardDataPresenter, @@ -182,10 +182,10 @@ describe('AnalyticsService', () => { it('getDashboardData throws with details message on error', async () => { const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, - { execute: vi.fn() } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -197,10 +197,10 @@ describe('AnalyticsService', () => { it('getDashboardData throws with fallback message when no details.message', async () => { const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -224,10 +224,10 @@ describe('AnalyticsService', () => { }; const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - getAnalyticsMetricsUseCase as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + getAnalyticsMetricsUseCase as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -244,10 +244,10 @@ describe('AnalyticsService', () => { it('getAnalyticsMetrics throws with details message on error', async () => { const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), @@ -259,10 +259,10 @@ describe('AnalyticsService', () => { it('getAnalyticsMetrics throws with fallback message when no details.message', async () => { const service = new AnalyticsService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, new RecordPageViewPresenter(), new RecordEngagementPresenter(), new GetDashboardDataPresenter(), diff --git a/apps/api/src/domain/auth/ActorFromSession.test.ts b/apps/api/src/domain/auth/ActorFromSession.test.ts index 67497e6d3..9c3282efa 100644 --- a/apps/api/src/domain/auth/ActorFromSession.test.ts +++ b/apps/api/src/domain/auth/ActorFromSession.test.ts @@ -8,7 +8,7 @@ async function withRequestContext(req: Record, fn: () => Pro const res = {}; return await new Promise((resolve, reject) => { - requestContextMiddleware(req as any, res as any, () => { + requestContextMiddleware(req as never, res as never, () => { fn().then(resolve, reject); }); }); @@ -16,12 +16,12 @@ async function withRequestContext(req: Record, fn: () => Pro describe('ActorFromSession', () => { it('derives actor from authenticated session (request.user), not request payload', async () => { - const req: any = { + const req: unknown = { user: { userId: 'driver-from-session' }, body: { driverId: 'driver-from-body' }, }; - await withRequestContext(req, async () => { + await withRequestContext(req as Record, async () => { const actor = getActorFromRequestContext(); expect(actor).toEqual({ userId: 'driver-from-session', driverId: 'driver-from-session' }); }); @@ -32,14 +32,14 @@ describe('ActorFromSession', () => { execute: vi.fn(async () => Result.ok(undefined)), }; - const req: any = { + const req: unknown = { user: { userId: 'driver-from-session' }, body: { performerDriverId: 'driver-from-body' }, }; - await withRequestContext(req, async () => { + await withRequestContext(req as Record, async () => { await expect( - requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as any), + requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as never), ).resolves.toBeUndefined(); }); @@ -52,15 +52,15 @@ describe('ActorFromSession', () => { it('permission helper rejects when league admin check fails', async () => { const getLeagueAdminPermissionsUseCase = { execute: vi.fn(async () => - Result.err({ code: 'USER_NOT_MEMBER', details: { message: 'nope' } } as any), + Result.err({ code: 'USER_NOT_MEMBER', details: { message: 'nope' } } as never), ), }; - const req: any = { user: { userId: 'driver-from-session' } }; + const req: unknown = { user: { userId: 'driver-from-session' } }; - await withRequestContext(req, async () => { + await withRequestContext(req as Record, async () => { await expect( - requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as any), + requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as never), ).rejects.toThrow('Forbidden'); }); }); diff --git a/apps/api/src/domain/auth/AuthController.test.ts b/apps/api/src/domain/auth/AuthController.test.ts index ea3644a62..29b0ea4a1 100644 --- a/apps/api/src/domain/auth/AuthController.test.ts +++ b/apps/api/src/domain/auth/AuthController.test.ts @@ -118,7 +118,7 @@ describe('AuthController', () => { }; (service.getCurrentSession as Mock).mockResolvedValue(session); - const res = { json: vi.fn() } as any; + const res = { json: vi.fn() } as unknown as import('express').Response; await controller.getSession(res); @@ -129,7 +129,7 @@ describe('AuthController', () => { it('should write JSON null when no session', async () => { (service.getCurrentSession as Mock).mockResolvedValue(null); - const res = { json: vi.fn() } as any; + const res = { json: vi.fn() } as unknown as import('express').Response; await controller.getSession(res); @@ -151,7 +151,7 @@ describe('AuthController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -201,9 +201,9 @@ describe('AuthController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/auth/AuthGuards.http.test.ts b/apps/api/src/domain/auth/AuthGuards.http.test.ts index 15c8cd64c..8b723b502 100644 --- a/apps/api/src/domain/auth/AuthGuards.http.test.ts +++ b/apps/api/src/domain/auth/AuthGuards.http.test.ts @@ -45,7 +45,7 @@ type SessionPort = { }; describe('Auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: SessionPort = { getCurrentSession: vi.fn(async () => null), @@ -75,9 +75,9 @@ describe('Auth guards (HTTP)', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/auth/AuthService.test.ts b/apps/api/src/domain/auth/AuthService.test.ts index 63459efc4..1f55c4692 100644 --- a/apps/api/src/domain/auth/AuthService.test.ts +++ b/apps/api/src/domain/auth/AuthService.test.ts @@ -3,11 +3,11 @@ import { Result } from '@core/shared/application/Result'; import { AuthService } from './AuthService'; class FakeAuthSessionPresenter { - private model: any = null; + private model: unknown = null; reset() { this.model = null; } - present(model: any) { + present(model: unknown) { this.model = model; } get responseModel() { @@ -17,11 +17,11 @@ class FakeAuthSessionPresenter { } class FakeCommandResultPresenter { - private model: any = null; + private model: unknown = null; reset() { this.model = null; } - present(model: any) { + present(model: unknown) { this.model = model; } get responseModel() { @@ -33,18 +33,18 @@ class FakeCommandResultPresenter { describe('AuthService', () => { it('getCurrentSession returns null when no session', async () => { const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { getCurrentSession: vi.fn(async () => null), createSession: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { getCurrentSession: vi.fn(async () => null), createSession: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); await expect(service.getCurrentSession()).resolves.toBeNull(); @@ -52,24 +52,24 @@ describe('AuthService', () => { it('getCurrentSession maps core session to DTO', async () => { const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, { getCurrentSession: vi.fn(async () => ({ token: 't1', user: { id: 'u1', email: null, displayName: 'John' }, })), createSession: vi.fn(), - } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); await expect(service.getCurrentSession()).resolves.toEqual({ @@ -92,25 +92,25 @@ describe('AuthService', () => { }; const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - identitySessionPort as any, - { execute: vi.fn() } as any, - signupUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - authSessionPresenter as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + identitySessionPort as never, + { execute: vi.fn() } as never, + signupUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + authSessionPresenter as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); const session = await service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'Jane Smith', - } as any); + } as never); expect(signupUseCase.execute).toHaveBeenCalledWith({ email: 'e2', @@ -127,22 +127,22 @@ describe('AuthService', () => { it('signupWithEmail throws with fallback when no details.message', async () => { const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { getCurrentSession: vi.fn(), createSession: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { getCurrentSession: vi.fn(), createSession: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); await expect( - service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'Jane Smith' } as any), + service.signupWithEmail({ email: 'e2', password: 'p2', displayName: 'Jane Smith' } as never), ).rejects.toThrow('Signup failed'); }); @@ -160,21 +160,21 @@ describe('AuthService', () => { }; const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - identitySessionPort as any, - loginUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - authSessionPresenter as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + identitySessionPort as never, + loginUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + authSessionPresenter as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); - await expect(service.loginWithEmail({ email: 'e3', password: 'p3' } as any)).resolves.toEqual({ + await expect(service.loginWithEmail({ email: 'e3', password: 'p3' } as never)).resolves.toEqual({ token: 't3', user: { userId: 'u3', email: 'e3', displayName: 'Bob Wilson' }, }); @@ -192,40 +192,40 @@ describe('AuthService', () => { it('loginWithEmail throws on use case error and prefers details.message', async () => { const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { getCurrentSession: vi.fn(), createSession: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS', details: { message: 'Bad login' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { getCurrentSession: vi.fn(), createSession: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS', details: { message: 'Bad login' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); - await expect(service.loginWithEmail({ email: 'e', password: 'p' } as any)).rejects.toThrow('Bad login'); + await expect(service.loginWithEmail({ email: 'e', password: 'p' } as never)).rejects.toThrow('Bad login'); }); it('loginWithEmail throws with fallback when no details.message', async () => { const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { getCurrentSession: vi.fn(), createSession: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { getCurrentSession: vi.fn(), createSession: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'INVALID_CREDENTIALS' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); - await expect(service.loginWithEmail({ email: 'e', password: 'p' } as any)).rejects.toThrow('Login failed'); + await expect(service.loginWithEmail({ email: 'e', password: 'p' } as never)).rejects.toThrow('Login failed'); }); it('logout returns command result on success', async () => { @@ -237,18 +237,18 @@ describe('AuthService', () => { }; const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { getCurrentSession: vi.fn(), createSession: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logoutUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - commandResultPresenter as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { getCurrentSession: vi.fn(), createSession: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logoutUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + commandResultPresenter as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); await expect(service.logout()).resolves.toEqual({ success: true }); @@ -256,18 +256,18 @@ describe('AuthService', () => { it('logout throws with fallback when no details.message', async () => { const service = new AuthService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { getCurrentSession: vi.fn(), createSession: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - new FakeAuthSessionPresenter() as any, - new FakeCommandResultPresenter() as any, - new FakeAuthSessionPresenter() as any, - new FakeAuthSessionPresenter() as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { getCurrentSession: vi.fn(), createSession: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + new FakeAuthSessionPresenter() as never, + new FakeCommandResultPresenter() as never, + new FakeAuthSessionPresenter() as never, + new FakeAuthSessionPresenter() as never, ); await expect(service.logout()).rejects.toThrow('Logout failed'); diff --git a/apps/api/src/domain/auth/AuthSession.http.test.ts b/apps/api/src/domain/auth/AuthSession.http.test.ts index 86b5aa762..3b2623241 100644 --- a/apps/api/src/domain/auth/AuthSession.http.test.ts +++ b/apps/api/src/domain/auth/AuthSession.http.test.ts @@ -9,7 +9,7 @@ import { requestContextMiddleware } from '@adapters/http/RequestContext'; import { AuthModule } from './AuthModule'; describe('Auth session (HTTP, inmemory)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; beforeEach(async () => { const module = await Test.createTestingModule({ diff --git a/apps/api/src/domain/auth/AuthenticationGuard.test.ts b/apps/api/src/domain/auth/AuthenticationGuard.test.ts index c53a2df32..900b98347 100644 --- a/apps/api/src/domain/auth/AuthenticationGuard.test.ts +++ b/apps/api/src/domain/auth/AuthenticationGuard.test.ts @@ -11,44 +11,44 @@ function createExecutionContext(request: Record) { describe('AuthenticationGuard', () => { it('attaches request.user.userId from session when missing', async () => { - const request: any = {}; + const request: unknown = {}; const sessionPort = { getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })), }; - const guard = new AuthenticationGuard(sessionPort as any); + const guard = new AuthenticationGuard(sessionPort as never); - await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true); + await expect(guard.canActivate(createExecutionContext(request as Record) as never)).resolves.toBe(true); expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1); - expect(request.user).toEqual({ userId: 'user-1' }); + expect((request as { user?: unknown }).user).toEqual({ userId: 'user-1' }); }); it('does not override request.user.userId if already present', async () => { - const request: any = { user: { userId: 'already-set' } }; + const request: unknown = { user: { userId: 'already-set' } }; const sessionPort = { getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })), }; - const guard = new AuthenticationGuard(sessionPort as any); + const guard = new AuthenticationGuard(sessionPort as never); - await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true); + await expect(guard.canActivate(createExecutionContext(request as Record) as never)).resolves.toBe(true); expect(sessionPort.getCurrentSession).not.toHaveBeenCalled(); - expect(request.user).toEqual({ userId: 'already-set' }); + expect((request as { user?: unknown }).user).toEqual({ userId: 'already-set' }); }); it('leaves request.user undefined when no session exists', async () => { - const request: any = {}; + const request: unknown = {}; const sessionPort = { getCurrentSession: vi.fn(async () => null), }; - const guard = new AuthenticationGuard(sessionPort as any); + const guard = new AuthenticationGuard(sessionPort as never); - await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true); + await expect(guard.canActivate(createExecutionContext(request as Record) as never)).resolves.toBe(true); expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1); - expect(request.user).toBeUndefined(); + expect((request as { user?: unknown }).user).toBeUndefined(); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/auth/AuthorizationGuard.test.ts b/apps/api/src/domain/auth/AuthorizationGuard.test.ts index 589e50df5..09a6c6cd7 100644 --- a/apps/api/src/domain/auth/AuthorizationGuard.test.ts +++ b/apps/api/src/domain/auth/AuthorizationGuard.test.ts @@ -30,60 +30,60 @@ function createExecutionContext(options: { handler: Function; userId?: string }) describe('AuthorizationGuard', () => { it('allows public routes without a user session', async () => { const authorizationService = { getRolesForUser: vi.fn() }; - const guard = new AuthorizationGuard(new Reflector(), authorizationService as any); + const guard = new AuthorizationGuard(new Reflector(), authorizationService as never); const ctx = createExecutionContext({ handler: DummyController.prototype.publicHandler, }); - await expect(guard.canActivate(ctx as any)).resolves.toBe(true); + await expect(guard.canActivate(ctx as never)).resolves.toBe(true); expect(authorizationService.getRolesForUser).not.toHaveBeenCalled(); }); it('denies non-public routes by default when not authenticated', async () => { const authorizationService = { getRolesForUser: vi.fn() }; - const guard = new AuthorizationGuard(new Reflector(), authorizationService as any); + const guard = new AuthorizationGuard(new Reflector(), authorizationService as never); const ctx = createExecutionContext({ handler: DummyController.prototype.protectedHandler, }); - await expect(guard.canActivate(ctx as any)).rejects.toThrow(UnauthorizedException); + await expect(guard.canActivate(ctx as never)).rejects.toThrow(UnauthorizedException); }); it('allows non-public routes when authenticated', async () => { const authorizationService = { getRolesForUser: vi.fn().mockReturnValue([]) }; - const guard = new AuthorizationGuard(new Reflector(), authorizationService as any); + const guard = new AuthorizationGuard(new Reflector(), authorizationService as never); const ctx = createExecutionContext({ handler: DummyController.prototype.protectedHandler, userId: 'user-1', }); - await expect(guard.canActivate(ctx as any)).resolves.toBe(true); + await expect(guard.canActivate(ctx as never)).resolves.toBe(true); }); it('denies routes requiring roles when user does not have any required role', async () => { const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['user']) }; - const guard = new AuthorizationGuard(new Reflector(), authorizationService as any); + const guard = new AuthorizationGuard(new Reflector(), authorizationService as never); const ctx = createExecutionContext({ handler: DummyController.prototype.adminHandler, userId: 'user-1', }); - await expect(guard.canActivate(ctx as any)).rejects.toThrow(ForbiddenException); + await expect(guard.canActivate(ctx as never)).rejects.toThrow(ForbiddenException); }); it('allows routes requiring roles when user has a required role', async () => { const authorizationService = { getRolesForUser: vi.fn().mockReturnValue(['admin']) }; - const guard = new AuthorizationGuard(new Reflector(), authorizationService as any); + const guard = new AuthorizationGuard(new Reflector(), authorizationService as never); const ctx = createExecutionContext({ handler: DummyController.prototype.adminHandler, userId: 'user-1', }); - await expect(guard.canActivate(ctx as any)).resolves.toBe(true); + await expect(guard.canActivate(ctx as never)).resolves.toBe(true); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/bootstrap/BootstrapModule.postgres-seed.test.ts b/apps/api/src/domain/bootstrap/BootstrapModule.postgres-seed.test.ts index d37a0b753..6f575e1d0 100644 --- a/apps/api/src/domain/bootstrap/BootstrapModule.postgres-seed.test.ts +++ b/apps/api/src/domain/bootstrap/BootstrapModule.postgres-seed.test.ts @@ -67,12 +67,12 @@ describe('BootstrapModule Postgres racing seed gating (unit)', () => { const seedDemoUsersExecute = vi.fn(async () => undefined); const bootstrapModule = new BootstrapModule( - { execute: ensureExecute } as any, - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, + { execute: ensureExecute } as never, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, { leagueRepository: { countAll: leagueCountAll }, - } as any, - { execute: seedDemoUsersExecute } as any, + } as never, + { execute: seedDemoUsersExecute } as never, ); await bootstrapModule.onModuleInit(); diff --git a/apps/api/src/domain/bootstrap/BootstrapModule.test.ts b/apps/api/src/domain/bootstrap/BootstrapModule.test.ts index cf4f6ea7f..f1ec26c56 100644 --- a/apps/api/src/domain/bootstrap/BootstrapModule.test.ts +++ b/apps/api/src/domain/bootstrap/BootstrapModule.test.ts @@ -77,12 +77,12 @@ describe('BootstrapModule demo user seed integration (unit)', () => { const leagueCountAll = vi.fn(async () => leaguesCount); const bootstrapModule = new BootstrapModule( - { execute: ensureExecute } as any, - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, + { execute: ensureExecute } as never, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, { leagueRepository: { countAll: leagueCountAll }, - } as any, - { execute: seedDemoUsersExecute } as any, + } as never, + { execute: seedDemoUsersExecute } as never, ); await bootstrapModule.onModuleInit(); diff --git a/apps/api/src/domain/bootstrap/BootstrapSeed.http.test.ts b/apps/api/src/domain/bootstrap/BootstrapSeed.http.test.ts index 450b11afc..8ca418c4b 100644 --- a/apps/api/src/domain/bootstrap/BootstrapSeed.http.test.ts +++ b/apps/api/src/domain/bootstrap/BootstrapSeed.http.test.ts @@ -9,7 +9,7 @@ describe('Bootstrap seeding (HTTP, inmemory)', () => { const originalEnv = { ...process.env }; let module: TestingModule | undefined; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); diff --git a/apps/api/src/domain/dashboard/Dashboard.http.test.ts b/apps/api/src/domain/dashboard/Dashboard.http.test.ts index af81c179f..9b89ae7b8 100644 --- a/apps/api/src/domain/dashboard/Dashboard.http.test.ts +++ b/apps/api/src/domain/dashboard/Dashboard.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Dashboard domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Dashboard domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/dashboard/DashboardController.test.ts b/apps/api/src/domain/dashboard/DashboardController.test.ts index 74ad449c8..aa4d381f5 100644 --- a/apps/api/src/domain/dashboard/DashboardController.test.ts +++ b/apps/api/src/domain/dashboard/DashboardController.test.ts @@ -53,7 +53,7 @@ describe('DashboardController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -108,9 +108,9 @@ describe('DashboardController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/dashboard/DashboardService.test.ts b/apps/api/src/domain/dashboard/DashboardService.test.ts index 03d260005..c617175bd 100644 --- a/apps/api/src/domain/dashboard/DashboardService.test.ts +++ b/apps/api/src/domain/dashboard/DashboardService.test.ts @@ -9,9 +9,9 @@ describe('DashboardService', () => { const useCase = { execute: vi.fn(async () => Result.ok(mockResult)) }; const service = new DashboardService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - useCase as any, - presenter as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + useCase as never, + presenter as never, ); await expect(service.getDashboardOverview('d1')).resolves.toEqual({ feed: [] }); @@ -21,9 +21,9 @@ describe('DashboardService', () => { it('getDashboardOverview throws with details message on error', async () => { const service = new DashboardService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, - { present: vi.fn(), getResponseModel: vi.fn() } as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never, + { present: vi.fn(), getResponseModel: vi.fn() } as never, ); await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: boom'); @@ -31,9 +31,9 @@ describe('DashboardService', () => { it('getDashboardOverview throws with fallback message when no details.message', async () => { const service = new DashboardService( - { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { present: vi.fn(), getResponseModel: vi.fn() } as any, + { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { present: vi.fn(), getResponseModel: vi.fn() } as never, ); await expect(service.getDashboardOverview('d1')).rejects.toThrow('Failed to get dashboard overview: Unknown error'); diff --git a/apps/api/src/domain/driver/Driver.http.test.ts b/apps/api/src/domain/driver/Driver.http.test.ts index 460e5e1bf..5fd448db6 100644 --- a/apps/api/src/domain/driver/Driver.http.test.ts +++ b/apps/api/src/domain/driver/Driver.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Driver domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Driver domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/driver/DriverController.test.ts b/apps/api/src/domain/driver/DriverController.test.ts index 9d6f3fbed..a518b6a90 100644 --- a/apps/api/src/domain/driver/DriverController.test.ts +++ b/apps/api/src/domain/driver/DriverController.test.ts @@ -186,7 +186,7 @@ describe('DriverController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -225,9 +225,9 @@ describe('DriverController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/driver/DriverProviders.ts b/apps/api/src/domain/driver/DriverProviders.ts index 7e219951f..649553d04 100644 --- a/apps/api/src/domain/driver/DriverProviders.ts +++ b/apps/api/src/domain/driver/DriverProviders.ts @@ -17,6 +17,7 @@ import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort'; import { CompleteDriverOnboardingUseCase } from '@core/racing/application/use-cases/CompleteDriverOnboardingUseCase'; import { GetDriversLeaderboardUseCase } from '@core/racing/application/use-cases/GetDriversLeaderboardUseCase'; import { GetDriverLiveriesUseCase } from '@core/racing/application/use-cases/GetDriverLiveriesUseCase'; +import { GetDriverUseCase } from '@core/racing/application/use-cases/GetDriverUseCase'; import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/GetProfileOverviewUseCase'; import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase'; import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase'; @@ -67,6 +68,7 @@ import { GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_TOTAL_DRIVERS_USE_CASE_TOKEN, GET_DRIVER_LIVERIES_USE_CASE_TOKEN, + GET_DRIVER_USE_CASE_TOKEN, COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, @@ -276,4 +278,9 @@ export const DriverProviders: Provider[] = createLoggedProviders([ new GetDriverLiveriesUseCase(liveryRepository, logger), inject: [LIVERY_REPOSITORY_TOKEN, LOGGER_TOKEN], }, + { + provide: GET_DRIVER_USE_CASE_TOKEN, + useFactory: (driverRepo: IDriverRepository) => new GetDriverUseCase(driverRepo), + inject: [DRIVER_REPOSITORY_TOKEN], + }, ], initLogger); diff --git a/apps/api/src/domain/driver/DriverService.test.ts b/apps/api/src/domain/driver/DriverService.test.ts index 1f592f752..38a60f6da 100644 --- a/apps/api/src/domain/driver/DriverService.test.ts +++ b/apps/api/src/domain/driver/DriverService.test.ts @@ -14,9 +14,7 @@ describe('DriverService', () => { const isDriverRegisteredForRaceUseCase = { execute: vi.fn() }; const updateDriverProfileUseCase = { execute: vi.fn() }; const getProfileOverviewUseCase = { execute: vi.fn() }; - - // Mock for repository - const driverRepository = { findById: vi.fn() }; + const getDriverUseCase = { execute: vi.fn() }; // Mocks for presenters const driversLeaderboardPresenter = { present: vi.fn(), getResponseModel: vi.fn() }; @@ -38,8 +36,7 @@ describe('DriverService', () => { isDriverRegisteredForRaceUseCase.execute.mockResolvedValue(Result.ok(undefined)); updateDriverProfileUseCase.execute.mockResolvedValue(Result.ok(undefined)); getProfileOverviewUseCase.execute.mockResolvedValue(Result.ok(undefined)); - - driverRepository.findById.mockResolvedValue(null); + getDriverUseCase.execute.mockResolvedValue(Result.ok(null)); driversLeaderboardPresenter.getResponseModel.mockReturnValue({ items: [] }); driverStatsPresenter.getResponseModel.mockReturnValue({ totalDrivers: 0 }); @@ -52,22 +49,22 @@ describe('DriverService', () => { const createService = () => { return new DriverService( - getDriversLeaderboardUseCase as any, - getTotalDriversUseCase as any, - getDriverLiveriesUseCase as any, - completeDriverOnboardingUseCase as any, - isDriverRegisteredForRaceUseCase as any, - updateDriverProfileUseCase as any, - getProfileOverviewUseCase as any, - driverRepository as any, - logger as any, - driversLeaderboardPresenter as any, - driverStatsPresenter as any, - completeOnboardingPresenter as any, - driverRegistrationStatusPresenter as any, - driverPresenter as any, - driverProfilePresenter as any, - getDriverLiveriesPresenter as any, + getDriversLeaderboardUseCase as never, + getTotalDriversUseCase as never, + getDriverLiveriesUseCase as never, + completeDriverOnboardingUseCase as never, + isDriverRegisteredForRaceUseCase as never, + updateDriverProfileUseCase as never, + getProfileOverviewUseCase as never, + getDriverUseCase as never, + logger as never, + driversLeaderboardPresenter as never, + driverStatsPresenter as never, + completeOnboardingPresenter as never, + driverRegistrationStatusPresenter as never, + driverPresenter as never, + driverProfilePresenter as never, + getDriverLiveriesPresenter as never, ); }; @@ -97,7 +94,7 @@ describe('DriverService', () => { lastName: 'L', displayName: 'D', country: 'DE', - } as any); + } as never); expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({ userId: 'u1', @@ -115,7 +112,7 @@ describe('DriverService', () => { displayName: 'D', country: 'DE', bio: 'bio', - } as any); + } as never); expect(completeDriverOnboardingUseCase.execute).toHaveBeenCalledWith({ userId: 'u1', @@ -132,19 +129,19 @@ describe('DriverService', () => { driverRegistrationStatusPresenter.getResponseModel.mockReturnValue({ isRegistered: true }); await expect( - service.getDriverRegistrationStatus({ raceId: 'r1', driverId: 'd1' } as any), + service.getDriverRegistrationStatus({ raceId: 'r1', driverId: 'd1' } as never), ).resolves.toEqual({ isRegistered: true }); expect(isDriverRegisteredForRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', driverId: 'd1' }); expect(driverRegistrationStatusPresenter.getResponseModel).toHaveBeenCalled(); }); - it('getCurrentDriver calls repository and returns presenter model', async () => { + it('getCurrentDriver calls use case and returns presenter model', async () => { const service = createService(); - driverRepository.findById.mockResolvedValue(null); + getDriverUseCase.execute.mockResolvedValue(Result.ok(null)); await expect(service.getCurrentDriver('u1')).resolves.toBeNull(); - expect(driverRepository.findById).toHaveBeenCalledWith('u1'); + expect(getDriverUseCase.execute).toHaveBeenCalledWith({ driverId: 'u1' }); expect(driverPresenter.getResponseModel).toHaveBeenCalled(); }); @@ -173,12 +170,12 @@ describe('DriverService', () => { expect(driverPresenter.getResponseModel).toHaveBeenCalled(); }); - it('getDriver calls repository and returns presenter model', async () => { + it('getDriver calls use case and returns presenter model', async () => { const service = createService(); - driverRepository.findById.mockResolvedValue(null); + getDriverUseCase.execute.mockResolvedValue(Result.ok(null)); await expect(service.getDriver('d1')).resolves.toBeNull(); - expect(driverRepository.findById).toHaveBeenCalledWith('d1'); + expect(getDriverUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1' }); }); it('getDriverProfile executes use case and returns presenter model', async () => { diff --git a/apps/api/src/domain/driver/DriverService.ts b/apps/api/src/domain/driver/DriverService.ts index e477f9207..5d6e66748 100644 --- a/apps/api/src/domain/driver/DriverService.ts +++ b/apps/api/src/domain/driver/DriverService.ts @@ -18,6 +18,7 @@ import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/Ge import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase'; import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase'; import { UpdateDriverProfileUseCase, type UpdateDriverProfileInput } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase'; +import { GetDriverUseCase } from '@core/racing/application/use-cases/GetDriverUseCase'; // Presenters import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter'; @@ -29,11 +30,9 @@ import { DriverStatsPresenter } from './presenters/DriverStatsPresenter'; import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter'; // Tokens -import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; import type { Logger } from '@core/shared/application'; import { COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN, - DRIVER_REPOSITORY_TOKEN, GET_DRIVER_LIVERIES_USE_CASE_TOKEN, GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN, GET_PROFILE_OVERVIEW_USE_CASE_TOKEN, @@ -41,6 +40,7 @@ import { IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN, LOGGER_TOKEN, UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN, + GET_DRIVER_USE_CASE_TOKEN, } from './DriverTokens'; @Injectable() @@ -60,8 +60,8 @@ export class DriverService { private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase, @Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN) private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase, - @Inject(DRIVER_REPOSITORY_TOKEN) - private readonly driverRepository: IDriverRepository, // TODO must be removed from service + @Inject(GET_DRIVER_USE_CASE_TOKEN) + private readonly getDriverUseCase: GetDriverUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, // Injected presenters (optional for module test compatibility) @@ -138,8 +138,11 @@ export class DriverService { async getCurrentDriver(userId: string): Promise { this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`); - const driver = await this.driverRepository.findById(userId); - await this.driverPresenter!.present(Result.ok(driver)); + const result = await this.getDriverUseCase.execute({ driverId: userId }); + if (result.isErr()) { + throw new Error(result.unwrapErr().message); + } + await this.driverPresenter!.present(Result.ok(result.unwrap())); return this.driverPresenter!.getResponseModel(); } @@ -157,15 +160,22 @@ export class DriverService { await this.updateDriverProfileUseCase.execute(input); // Get the updated driver and present it - const driver = await this.driverRepository.findById(driverId); - await this.driverPresenter!.present(Result.ok(driver)); + const result = await this.getDriverUseCase.execute({ driverId }); + if (result.isErr()) { + throw new Error(result.unwrapErr().message); + } + await this.driverPresenter!.present(Result.ok(result.unwrap())); return this.driverPresenter!.getResponseModel(); } async getDriver(driverId: string): Promise { this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`); - const driver = await this.driverRepository.findById(driverId); + const result = await this.getDriverUseCase.execute({ driverId }); + if (result.isErr()) { + throw new Error(result.unwrapErr().message); + } + const driver = result.unwrap(); if (!driver) { return null; } @@ -193,4 +203,4 @@ export class DriverService { await this.getDriverLiveriesPresenter!.present(result); return this.getDriverLiveriesPresenter!.getResponseModel()!; } -} \ No newline at end of file +} diff --git a/apps/api/src/domain/driver/DriverTokens.ts b/apps/api/src/domain/driver/DriverTokens.ts index 1fc73e683..b1dfdca62 100644 --- a/apps/api/src/domain/driver/DriverTokens.ts +++ b/apps/api/src/domain/driver/DriverTokens.ts @@ -26,6 +26,7 @@ export const IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN = 'IsDriverRegisteredF export const UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN = 'UpdateDriverProfileUseCase'; export const GET_PROFILE_OVERVIEW_USE_CASE_TOKEN = 'GetProfileOverviewUseCase'; export const GET_DRIVER_LIVERIES_USE_CASE_TOKEN = 'GetDriverLiveriesUseCase'; +export const GET_DRIVER_USE_CASE_TOKEN = 'GetDriverUseCase'; export const GET_DRIVERS_LEADERBOARD_OUTPUT_PORT_TOKEN = 'GetDriversLeaderboardOutputPort_TOKEN'; export const GET_TOTAL_DRIVERS_OUTPUT_PORT_TOKEN = 'GetTotalDriversOutputPort_TOKEN'; diff --git a/apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.test.ts b/apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.test.ts index 45cfd7c6c..23b94bd1f 100644 --- a/apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.test.ts +++ b/apps/api/src/domain/driver/presenters/CompleteOnboardingPresenter.test.ts @@ -12,7 +12,7 @@ describe('CompleteOnboardingPresenter', () => { const result = { driver: { id: 'driver-123', - } as any, + } as never, }; presenter.present(result); @@ -27,7 +27,7 @@ describe('CompleteOnboardingPresenter', () => { const result = { driver: { id: 'driver-456', - } as any, + } as never, }; presenter.present(result); @@ -45,7 +45,7 @@ describe('CompleteOnboardingPresenter', () => { }); it('should return model after present()', () => { - presenter.present({ driver: { id: 'driver-123' } } as any); + presenter.present({ driver: { id: 'driver-123' } } as never); expect(presenter.getResponseModel()).toEqual({ success: true, driverId: 'driver-123', diff --git a/apps/api/src/domain/hello/Hello.http.test.ts b/apps/api/src/domain/hello/Hello.http.test.ts index 0153c58a8..645b3b35a 100644 --- a/apps/api/src/domain/hello/Hello.http.test.ts +++ b/apps/api/src/domain/hello/Hello.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Hello domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Hello domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/league/LeagueController.test.ts b/apps/api/src/domain/league/LeagueController.test.ts index 13cc92f38..b865b43d1 100644 --- a/apps/api/src/domain/league/LeagueController.test.ts +++ b/apps/api/src/domain/league/LeagueController.test.ts @@ -37,7 +37,7 @@ describe('LeagueController', () => { it('getTotalLeagues should return total leagues', async () => { const mockResult = { totalLeagues: 1 }; - leagueService.getTotalLeagues.mockResolvedValue(mockResult as any); + leagueService.getTotalLeagues.mockResolvedValue(mockResult as never); const result = await controller.getTotalLeagues(); @@ -47,7 +47,7 @@ describe('LeagueController', () => { it('getAllLeaguesWithCapacity should return leagues and totalCount', async () => { const mockResult = { leagues: [], totalCount: 0 }; - leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as any); + leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as never); const result = await controller.getAllLeaguesWithCapacity(); @@ -57,7 +57,7 @@ describe('LeagueController', () => { it('getLeagueStandings should return standings', async () => { const mockResult = { standings: [] }; - leagueService.getLeagueStandings.mockResolvedValue(mockResult as any); + leagueService.getLeagueStandings.mockResolvedValue(mockResult as never); const result = await controller.getLeagueStandings('league-1'); @@ -66,7 +66,7 @@ describe('LeagueController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -105,9 +105,9 @@ describe('LeagueController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/league/LeagueRosterAdminRead.http.test.ts b/apps/api/src/domain/league/LeagueRosterAdminRead.http.test.ts index f02aa3bc6..edf4b7721 100644 --- a/apps/api/src/domain/league/LeagueRosterAdminRead.http.test.ts +++ b/apps/api/src/domain/league/LeagueRosterAdminRead.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('League roster admin read (HTTP, league-scoped)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('League roster admin read (HTTP, league-scoped)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); @@ -105,7 +105,7 @@ describe('League roster admin read (HTTP, league-scoped)', () => { expect(res.body).toEqual(expect.any(Array)); expect(res.body.length).toBeGreaterThan(0); - const first = res.body[0] as any; + const first = res.body[0] as unknown as { driver: { id: string, name: string, country: string }, role: string }; expect(first).toMatchObject({ driverId: expect.any(String), role: expect.any(String), @@ -133,8 +133,8 @@ describe('League roster admin read (HTTP, league-scoped)', () => { // Seed data may or may not include join requests for a given league. // Validate shape on first item if present. - if ((res.body as any[]).length > 0) { - const first = (res.body as any[])[0]; + if ((res.body as never[]).length > 0) { + const first = (res.body as unknown as { message?: string }[])[0]; expect(first).toMatchObject({ id: expect.any(String), leagueId: expect.any(String), @@ -146,8 +146,10 @@ describe('League roster admin read (HTTP, league-scoped)', () => { }, }); - if (first.message !== undefined) { - expect(first.message).toEqual(expect.any(String)); + if (first) { + if (first.message !== undefined) { + expect(first.message).toEqual(expect.any(String)); + } } } }); diff --git a/apps/api/src/domain/league/LeagueRosterJoinRequests.mutations.http.test.ts b/apps/api/src/domain/league/LeagueRosterJoinRequests.mutations.http.test.ts index 9dcbeab48..d602ad933 100644 --- a/apps/api/src/domain/league/LeagueRosterJoinRequests.mutations.http.test.ts +++ b/apps/api/src/domain/league/LeagueRosterJoinRequests.mutations.http.test.ts @@ -25,7 +25,7 @@ import { JoinRequest } from '@core/racing/domain/entities/JoinRequest'; import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership'; describe('League roster join request mutations (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -123,15 +123,16 @@ describe('League roster join request mutations (HTTP)', () => { app = module.createNestApplication(); // Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner(). - app.use(requestContextMiddleware as any); + app.use(requestContextMiddleware as never); // Test-only auth injection: emulate an authenticated session by setting request.user. - app.use((req: any, _res: any, next: any) => { - const userId = req.headers['x-test-user-id']; + app.use((req: unknown, _res: unknown, next: unknown) => { + const r = req as { headers: Record, user?: { userId: string } }; + const userId = r.headers['x-test-user-id']; if (typeof userId === 'string' && userId.length > 0) { - req.user = { userId }; + r.user = { userId }; } - next(); + (next as () => void)(); }); app.useGlobalPipes( @@ -144,8 +145,8 @@ describe('League roster join request mutations (HTTP)', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), ); await app.init(); @@ -202,7 +203,7 @@ describe('League roster join request mutations (HTTP)', () => { .expect(200); expect(Array.isArray(joinRequests.body)).toBe(true); - expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeUndefined(); + expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeUndefined(); const members = await request(app.getHttpServer()) .get('/leagues/league-1/admin/roster/members') @@ -210,7 +211,7 @@ describe('League roster join request mutations (HTTP)', () => { .expect(200); expect(Array.isArray(members.body)).toBe(true); - expect(members.body.some((m: any) => m.driverId === 'driver-2')).toBe(true); + expect(members.body.some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(true); }); it('reject removes request only; roster reads reflect changes', async () => { @@ -232,7 +233,7 @@ describe('League roster join request mutations (HTTP)', () => { .expect(200); expect(Array.isArray(joinRequests.body)).toBe(true); - expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeUndefined(); + expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeUndefined(); const members = await request(app.getHttpServer()) .get('/leagues/league-1/admin/roster/members') @@ -240,7 +241,7 @@ describe('League roster join request mutations (HTTP)', () => { .expect(200); expect(Array.isArray(members.body)).toBe(true); - expect(members.body.some((m: any) => m.driverId === 'driver-2')).toBe(false); + expect(members.body.some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(false); }); it('approve returns error when league is full and keeps request pending', async () => { @@ -264,6 +265,6 @@ describe('League roster join request mutations (HTTP)', () => { .expect(200); expect(Array.isArray(joinRequests.body)).toBe(true); - expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeDefined(); + expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeDefined(); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueRosterMembers.mutations.http.test.ts b/apps/api/src/domain/league/LeagueRosterMembers.mutations.http.test.ts index d139397ed..bdf17c816 100644 --- a/apps/api/src/domain/league/LeagueRosterMembers.mutations.http.test.ts +++ b/apps/api/src/domain/league/LeagueRosterMembers.mutations.http.test.ts @@ -24,7 +24,7 @@ import { Driver } from '@core/racing/domain/entities/Driver'; import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership'; describe('League roster member mutations (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -98,15 +98,16 @@ describe('League roster member mutations (HTTP)', () => { app = module.createNestApplication(); // Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner(). - app.use(requestContextMiddleware as any); + app.use(requestContextMiddleware as never); // Test-only auth injection: emulate an authenticated session by setting request.user. - app.use((req: any, _res: any, next: any) => { - const userId = req.headers['x-test-user-id']; + app.use((req: unknown, _res: unknown, next: unknown) => { + const r = req as { headers: Record, user?: { userId: string } }; + const userId = r.headers['x-test-user-id']; if (typeof userId === 'string' && userId.length > 0) { - req.user = { userId }; + r.user = { userId }; } - next(); + (next as () => void)(); }); app.useGlobalPipes( @@ -119,8 +120,8 @@ describe('League roster member mutations (HTTP)', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), ); await app.init(); @@ -173,9 +174,9 @@ describe('League roster member mutations (HTTP)', () => { expect(Array.isArray(members.body)).toBe(true); - const updated = (members.body as any[]).find(m => m.driverId === 'driver-2'); + const updated = (members.body as { driverId: string, role: string }[]).find((m: unknown) => (m as { driverId: string }).driverId === 'driver-2'); expect(updated).toBeDefined(); - expect(updated.role).toBe('steward'); + expect(updated?.role).toBe('steward'); }); it('member removal is reflected in roster members read', async () => { @@ -192,6 +193,6 @@ describe('League roster member mutations (HTTP)', () => { .expect(200); expect(Array.isArray(members.body)).toBe(true); - expect((members.body as any[]).some(m => m.driverId === 'driver-2')).toBe(false); + expect((members.body as never[]).some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(false); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueScheduleAdmin.http.test.ts b/apps/api/src/domain/league/LeagueScheduleAdmin.http.test.ts index a1b771ea9..bc385796d 100644 --- a/apps/api/src/domain/league/LeagueScheduleAdmin.http.test.ts +++ b/apps/api/src/domain/league/LeagueScheduleAdmin.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('League schedule admin CRUD (HTTP, season-scoped)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); @@ -152,7 +152,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => { const raceId: string = createRes.body.raceId; const afterCreateRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200); - const createdRace = (afterCreateRes.body.races as any[]).find((r) => r.id === raceId); + const createdRace = (afterCreateRes.body.races as { id: string }[]).find((r) => r.id === raceId); expect(createdRace).toMatchObject({ id: raceId, name: 'Test Track - Test Car', @@ -174,7 +174,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => { }); const afterUpdateRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200); - const updatedRace = (afterUpdateRes.body.races as any[]).find((r) => r.id === raceId); + const updatedRace = (afterUpdateRes.body.races as { id: string }[]).find((r) => r.id === raceId); expect(updatedRace).toMatchObject({ id: raceId, name: 'Updated Track - Updated Car', @@ -186,7 +186,7 @@ describe('League schedule admin CRUD (HTTP, season-scoped)', () => { }); const afterDeleteRes = await agent.get('/leagues/league-5/schedule?seasonId=league-5-season-1').expect(200); - const deletedRace = (afterDeleteRes.body.races as any[]).find((r) => r.id === raceId); + const deletedRace = (afterDeleteRes.body.races as { id: string }[]).find((r) => r.id === raceId); expect(deletedRace).toBeUndefined(); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueSchedulePublish.http.test.ts b/apps/api/src/domain/league/LeagueSchedulePublish.http.test.ts index 86f025106..eda059a3a 100644 --- a/apps/api/src/domain/league/LeagueSchedulePublish.http.test.ts +++ b/apps/api/src/domain/league/LeagueSchedulePublish.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('League season schedule publish/unpublish (HTTP, season-scoped)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('League season schedule publish/unpublish (HTTP, season-scoped)', () => }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/league/LeagueService.test.ts b/apps/api/src/domain/league/LeagueService.test.ts index 839a284fd..00aec8d5d 100644 --- a/apps/api/src/domain/league/LeagueService.test.ts +++ b/apps/api/src/domain/league/LeagueService.test.ts @@ -8,7 +8,7 @@ async function withUserId(userId: string, fn: () => Promise): Promise { const res = {}; return await new Promise((resolve, reject) => { - requestContextMiddleware(req as any, res as any, () => { + requestContextMiddleware(req as never, res as never, () => { fn().then(resolve, reject); }); }); @@ -19,13 +19,13 @@ describe('LeagueService', () => { const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }; const ok = async () => Result.ok(undefined); - const err = async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } }); + const err = async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never); - const getAllLeaguesWithCapacityUseCase: any = { execute: vi.fn(async () => Result.ok({ leagues: [] })) }; - const getAllLeaguesWithCapacityAndScoringUseCase: any = { execute: vi.fn(ok) }; + const getAllLeaguesWithCapacityUseCase = { execute: vi.fn(async () => Result.ok({ leagues: [] })) }; + const getAllLeaguesWithCapacityAndScoringUseCase = { execute: vi.fn(ok) }; const getLeagueStandingsUseCase = { execute: vi.fn(ok) }; const getLeagueStatsUseCase = { execute: vi.fn(ok) }; - const getLeagueFullConfigUseCase: any = { execute: vi.fn(ok) }; + const getLeagueFullConfigUseCase = { execute: vi.fn(ok) }; const getLeagueScoringConfigUseCase = { execute: vi.fn(ok) }; const listLeagueScoringPresetsUseCase = { execute: vi.fn(ok) }; const joinLeagueUseCase = { execute: vi.fn(ok) }; @@ -120,74 +120,74 @@ describe('LeagueService', () => { const publishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: true })) }; const unpublishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: false })) }; - const service = new (LeagueService as any)( - getAllLeaguesWithCapacityUseCase as any, - getAllLeaguesWithCapacityAndScoringUseCase as any, - getLeagueStandingsUseCase as any, - getLeagueStatsUseCase as any, - getLeagueFullConfigUseCase as any, - getLeagueScoringConfigUseCase as any, - listLeagueScoringPresetsUseCase as any, - joinLeagueUseCase as any, - transferLeagueOwnershipUseCase as any, - createLeagueWithSeasonAndScoringUseCase as any, - getTotalLeaguesUseCase as any, - getLeagueJoinRequestsUseCase as any, - approveLeagueJoinRequestUseCase as any, - rejectLeagueJoinRequestUseCase as any, - removeLeagueMemberUseCase as any, - updateLeagueMemberRoleUseCase as any, - getLeagueOwnerSummaryUseCase as any, - getLeagueProtestsUseCase as any, - getLeagueSeasonsUseCase as any, - getLeagueMembershipsUseCase as any, - getLeagueScheduleUseCase as any, - getLeagueAdminPermissionsUseCase as any, - getLeagueWalletUseCase as any, - withdrawFromLeagueWalletUseCase as any, - getSeasonSponsorshipsUseCase as any, - createLeagueSeasonScheduleRaceUseCase as any, - updateLeagueSeasonScheduleRaceUseCase as any, - deleteLeagueSeasonScheduleRaceUseCase as any, - publishLeagueSeasonScheduleUseCase as any, - unpublishLeagueSeasonScheduleUseCase as any, - logger as any, - allLeaguesWithCapacityPresenter as any, - allLeaguesWithCapacityAndScoringPresenter as any, - leagueStandingsPresenter as any, - leagueProtestsPresenter as any, - seasonSponsorshipsPresenter as any, - leagueScoringPresetsPresenter as any, - approveLeagueJoinRequestPresenter as any, - createLeaguePresenter as any, - getLeagueAdminPermissionsPresenter as any, - getLeagueMembershipsPresenter as any, - getLeagueOwnerSummaryPresenter as any, - getLeagueSeasonsPresenter as any, - joinLeaguePresenter as any, - leagueSchedulePresenter as any, - leagueStatsPresenter as any, - rejectLeagueJoinRequestPresenter as any, - removeLeagueMemberPresenter as any, - totalLeaguesPresenter as any, - transferLeagueOwnershipPresenter as any, - updateLeagueMemberRolePresenter as any, - leagueConfigPresenter as any, - leagueScoringConfigPresenter as any, - getLeagueWalletPresenter as any, - withdrawFromLeagueWalletPresenter as any, - leagueJoinRequestsPresenter as any, - createLeagueSeasonScheduleRacePresenter as any, - updateLeagueSeasonScheduleRacePresenter as any, - deleteLeagueSeasonScheduleRacePresenter as any, - publishLeagueSeasonSchedulePresenter as any, - unpublishLeagueSeasonSchedulePresenter as any, + const service = new (LeagueService as unknown as { new (...args: never[]): LeagueService })( + getAllLeaguesWithCapacityUseCase as never, + getAllLeaguesWithCapacityAndScoringUseCase as never, + getLeagueStandingsUseCase as never, + getLeagueStatsUseCase as never, + getLeagueFullConfigUseCase as never, + getLeagueScoringConfigUseCase as never, + listLeagueScoringPresetsUseCase as never, + joinLeagueUseCase as never, + transferLeagueOwnershipUseCase as never, + createLeagueWithSeasonAndScoringUseCase as never, + getTotalLeaguesUseCase as never, + getLeagueJoinRequestsUseCase as never, + approveLeagueJoinRequestUseCase as never, + rejectLeagueJoinRequestUseCase as never, + removeLeagueMemberUseCase as never, + updateLeagueMemberRoleUseCase as never, + getLeagueOwnerSummaryUseCase as never, + getLeagueProtestsUseCase as never, + getLeagueSeasonsUseCase as never, + getLeagueMembershipsUseCase as never, + getLeagueScheduleUseCase as never, + getLeagueAdminPermissionsUseCase as never, + getLeagueWalletUseCase as never, + withdrawFromLeagueWalletUseCase as never, + getSeasonSponsorshipsUseCase as never, + createLeagueSeasonScheduleRaceUseCase as never, + updateLeagueSeasonScheduleRaceUseCase as never, + deleteLeagueSeasonScheduleRaceUseCase as never, + publishLeagueSeasonScheduleUseCase as never, + unpublishLeagueSeasonScheduleUseCase as never, + logger as never, + allLeaguesWithCapacityPresenter as never, + allLeaguesWithCapacityAndScoringPresenter as never, + leagueStandingsPresenter as never, + leagueProtestsPresenter as never, + seasonSponsorshipsPresenter as never, + leagueScoringPresetsPresenter as never, + approveLeagueJoinRequestPresenter as never, + createLeaguePresenter as never, + getLeagueAdminPermissionsPresenter as never, + getLeagueMembershipsPresenter as never, + getLeagueOwnerSummaryPresenter as never, + getLeagueSeasonsPresenter as never, + joinLeaguePresenter as never, + leagueSchedulePresenter as never, + leagueStatsPresenter as never, + rejectLeagueJoinRequestPresenter as never, + removeLeagueMemberPresenter as never, + totalLeaguesPresenter as never, + transferLeagueOwnershipPresenter as never, + updateLeagueMemberRolePresenter as never, + leagueConfigPresenter as never, + leagueScoringConfigPresenter as never, + getLeagueWalletPresenter as never, + withdrawFromLeagueWalletPresenter as never, + leagueJoinRequestsPresenter as never, + createLeagueSeasonScheduleRacePresenter as never, + updateLeagueSeasonScheduleRacePresenter as never, + deleteLeagueSeasonScheduleRacePresenter as never, + publishLeagueSeasonSchedulePresenter as never, + unpublishLeagueSeasonSchedulePresenter as never, // Roster admin read delegation (added for strict TDD) - getLeagueRosterMembersUseCase as any, - getLeagueRosterJoinRequestsUseCase as any, - getLeagueRosterMembersPresenter as any, - getLeagueRosterJoinRequestsPresenter as any, + getLeagueRosterMembersUseCase as never, + getLeagueRosterJoinRequestsUseCase as never, + getLeagueRosterMembersPresenter as never, + getLeagueRosterJoinRequestsPresenter as never, ); await expect(service.getTotalLeagues()).resolves.toEqual({ total: 1 }); @@ -196,13 +196,13 @@ describe('LeagueService', () => { }); await withUserId('user-1', async () => { - await expect(service.approveLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as any)).resolves.toEqual({ + await expect(service.approveLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as never)).resolves.toEqual({ success: true, }); }); await withUserId('user-1', async () => { - await expect(service.rejectLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as any)).resolves.toEqual({ + await expect(service.rejectLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as never)).resolves.toEqual({ success: true, }); }); @@ -212,11 +212,11 @@ describe('LeagueService', () => { ); await withUserId('user-1', async () => { - await expect(service.getLeagueAdminPermissions({ leagueId: 'l1' } as any)).resolves.toEqual({ canManage: true }); + await expect(service.getLeagueAdminPermissions({ leagueId: 'l1' } as never)).resolves.toEqual({ canManage: true }); }); await withUserId('user-1', async () => { - await expect(service.removeLeagueMember({ leagueId: 'l1', targetDriverId: 'd1' } as any)).resolves.toEqual({ + await expect(service.removeLeagueMember({ leagueId: 'l1', targetDriverId: 'd1' } as never)).resolves.toEqual({ success: true, }); }); @@ -232,7 +232,7 @@ describe('LeagueService', () => { }); await withUserId('user-1', async () => { - await expect(service.updateLeagueMemberRole('l1', 'd1', { newRole: 'member' } as any)).resolves.toEqual({ + await expect(service.updateLeagueMemberRole('l1', 'd1', { newRole: 'member' } as never)).resolves.toEqual({ success: true, }); }); @@ -248,11 +248,11 @@ describe('LeagueService', () => { newRole: 'member', }); - await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as any)).resolves.toEqual({ ownerId: 'o1' }); - await expect(service.getLeagueProtests({ leagueId: 'l1' } as any)).resolves.toEqual({ protests: [] }); - await expect(service.getLeagueSeasons({ leagueId: 'l1' } as any)).resolves.toEqual([]); + await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as never)).resolves.toEqual({ ownerId: 'o1' }); + await expect(service.getLeagueProtests({ leagueId: 'l1' } as never)).resolves.toEqual({ protests: [] }); + await expect(service.getLeagueSeasons({ leagueId: 'l1' } as never)).resolves.toEqual([]); - await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toEqual({ form: {} }); + await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toEqual({ form: {} }); await expect(service.getLeagueScoringConfig('l1')).resolves.toEqual({ config: {} }); await expect(service.getLeagueMemberships('l1')).resolves.toEqual({ memberships: [] }); @@ -272,7 +272,7 @@ describe('LeagueService', () => { // Roster admin read endpoints must be admin-gated (auth boundary) and must not execute core use cases on 403. getLeagueAdminPermissionsUseCase.execute.mockResolvedValueOnce( - Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as any, + Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as never, ); getLeagueRosterMembersUseCase.execute.mockClear(); await withUserId('user-2', async () => { @@ -285,7 +285,7 @@ describe('LeagueService', () => { expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); getLeagueScheduleUseCase.execute.mockClear(); - await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as any)).resolves.toEqual({ + await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as never)).resolves.toEqual({ seasonId: 'season-1', published: false, races: [], @@ -293,7 +293,7 @@ describe('LeagueService', () => { expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 'season-x' }); await expect(service.getLeagueStats('l1')).resolves.toEqual({ stats: {} }); - await expect(service.createLeague({ name: 'n', description: 'd', ownerId: 'o' } as any)).resolves.toEqual({ id: 'l1' }); + await expect(service.createLeague({ name: 'n', description: 'd', ownerId: 'o' } as never)).resolves.toEqual({ id: 'l1' }); await expect(service.listLeagueScoringPresets()).resolves.toEqual({ presets: [] }); await withUserId('user-1', async () => { @@ -301,7 +301,7 @@ describe('LeagueService', () => { }); await withUserId('user-1', async () => { - await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as any)).resolves.toEqual({ success: true }); + await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as never)).resolves.toEqual({ success: true }); }); // Transfer ownership must be admin-gated and actor-derived (no payload owner/admin IDs) @@ -310,7 +310,7 @@ describe('LeagueService', () => { await withUserId('user-1', async () => { await expect( - service.transferLeagueOwnership('l1', { newOwnerId: 'o2', currentOwnerId: 'spoof' } as any), + service.transferLeagueOwnership('l1', { newOwnerId: 'o2', currentOwnerId: 'spoof' } as never), ).resolves.toEqual({ success: true }); }); @@ -327,11 +327,11 @@ describe('LeagueService', () => { // Unauthorized (non-admin/owner) actors are rejected getLeagueAdminPermissionsUseCase.execute.mockResolvedValueOnce( - Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as any, + Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as never, ); transferLeagueOwnershipUseCase.execute.mockClear(); await withUserId('user-2', async () => { - await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as any)).rejects.toThrow('Forbidden'); + await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as never)).rejects.toThrow('Forbidden'); }); expect(transferLeagueOwnershipUseCase.execute).not.toHaveBeenCalled(); @@ -349,11 +349,11 @@ describe('LeagueService', () => { await withUserId('user-1', async () => { await expect( - service.publishLeagueSeasonSchedule('l1', 'season-1', {} as any), + service.publishLeagueSeasonSchedule('l1', 'season-1', {} as never), ).resolves.toEqual({ success: true, published: true }); await expect( - service.unpublishLeagueSeasonSchedule('l1', 'season-1', {} as any), + service.unpublishLeagueSeasonSchedule('l1', 'season-1', {} as never), ).resolves.toEqual({ success: true, published: false }); await expect( @@ -361,14 +361,14 @@ describe('LeagueService', () => { track: 'Spa', car: 'GT3', scheduledAtIso: new Date('2025-01-10T20:00:00Z').toISOString(), - } as any), + } as never), ).resolves.toEqual({ raceId: 'race-1' }); await expect( service.updateLeagueSeasonScheduleRace('l1', 'season-1', 'race-1', { track: 'Monza', car: 'LMP2', - } as any), + } as never), ).resolves.toEqual({ success: true }); await expect( @@ -403,7 +403,7 @@ describe('LeagueService', () => { await withUserId('user-1', async () => { await expect( - service.withdrawFromLeagueWallet('l1', { amount: 1, currency: 'USD', destinationAccount: 'x' } as any), + service.withdrawFromLeagueWallet('l1', { amount: 1, currency: 'USD', destinationAccount: 'x' } as never), ).resolves.toEqual({ success: true, }); @@ -421,14 +421,14 @@ describe('LeagueService', () => { await expect(service.getAllLeaguesWithCapacityAndScoring()).resolves.toEqual({ leagues: [], totalCount: 0 }); // Error branch: getAllLeaguesWithCapacity throws on result.isErr() - getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })); + getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never)); await expect(service.getAllLeaguesWithCapacity()).rejects.toThrow('REPOSITORY_ERROR'); // Error branches: try/catch returning null getLeagueFullConfigUseCase.execute.mockImplementationOnce(async () => { throw new Error('boom'); }); - await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toBeNull(); + await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull(); getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => { throw new Error('boom'); @@ -439,7 +439,7 @@ describe('LeagueService', () => { getLeagueFullConfigUseCase.execute.mockImplementationOnce(async () => { throw 'boom'; }); - await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toBeNull(); + await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull(); getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => { throw 'boom'; @@ -447,7 +447,7 @@ describe('LeagueService', () => { await expect(service.getLeagueScoringConfig('l1')).resolves.toBeNull(); // getLeagueAdmin error branch: fullConfigResult is Err - getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })); + getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never)); await withUserId('user-1', async () => { await expect(service.getLeagueAdmin('l1')).rejects.toThrow('REPOSITORY_ERROR'); }); @@ -467,4 +467,4 @@ describe('LeagueService', () => { // keep lint happy (ensures err() used) await err(); }); -}); \ No newline at end of file +}); diff --git a/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityAndScoringPresenter.test.ts b/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityAndScoringPresenter.test.ts index 5e966fa46..efbd4ef82 100644 --- a/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityAndScoringPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/AllLeaguesWithCapacityAndScoringPresenter.test.ts @@ -168,8 +168,8 @@ describe('AllLeaguesWithCapacityAndScoringPresenter', () => { schedule: { startDate: new Date('2024-01-01'), timeOfDay: { hour: 20, minute: 0 }, - } as any, - } as any); + } as never, + } as never); const scoringConfig = LeagueScoringConfig.create({ id: 'scoring-1', @@ -264,8 +264,8 @@ describe('AllLeaguesWithCapacityAndScoringPresenter', () => { schedule: { startDate: new Date('2024-01-01'), timeOfDay: { hour: 20, minute: 0 }, - } as any, - } as any); + } as never, + } as never); const scoringConfig = LeagueScoringConfig.create({ id: 'scoring-1', @@ -464,7 +464,7 @@ describe('AllLeaguesWithCapacityAndScoringPresenter', () => { }); // Override logoRef to uploaded type - (league as any).logoRef = MediaReference.fromJSON({ + (league as unknown as { logoRef: unknown }).logoRef = MediaReference.fromJSON({ type: 'uploaded', mediaId: 'media-123', }); diff --git a/apps/api/src/domain/league/presenters/CreateLeaguePresenter.test.ts b/apps/api/src/domain/league/presenters/CreateLeaguePresenter.test.ts index 590d81ceb..e8f9a1c7f 100644 --- a/apps/api/src/domain/league/presenters/CreateLeaguePresenter.test.ts +++ b/apps/api/src/domain/league/presenters/CreateLeaguePresenter.test.ts @@ -28,7 +28,7 @@ describe('CreateLeaguePresenter', () => { gameId: 'iracing', name: 'Test League Season 1', status: 'active', - } as any); + } as never); const scoringConfig = LeagueScoringConfig.create({ id: 'scoring-1', @@ -80,7 +80,7 @@ describe('CreateLeaguePresenter', () => { gameId: 'iracing', name: 'Another League Season 1', status: 'active', - } as any); + } as never); const scoringConfig = LeagueScoringConfig.create({ id: 'scoring-2', @@ -138,7 +138,7 @@ describe('CreateLeaguePresenter', () => { gameId: 'iracing', name: 'Test League Season 1', status: 'active', - } as any); + } as never); const scoringConfig = LeagueScoringConfig.create({ id: 'scoring-1', @@ -190,7 +190,7 @@ describe('CreateLeaguePresenter', () => { gameId: 'iracing', name: 'Test League Season 1', status: 'active', - } as any); + } as never); const scoringConfig = LeagueScoringConfig.create({ id: 'scoring-1', diff --git a/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts index 8c9111237..7f102b755 100644 --- a/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/GetLeagueMembershipsPresenter.test.ts @@ -6,7 +6,7 @@ describe('GetLeagueMembershipsPresenter', () => { const presenter = new GetLeagueMembershipsPresenter(); const output: GetLeagueMembershipsResult = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - league: {} as any, + league: {} as never, memberships: [ { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -15,7 +15,7 @@ describe('GetLeagueMembershipsPresenter', () => { role: 'member', joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, + } as never, // eslint-disable-next-line @typescript-eslint/no-explicit-any driver: { id: 'driver-1', @@ -23,7 +23,7 @@ describe('GetLeagueMembershipsPresenter', () => { name: 'John Doe', country: 'US', joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') }, - } as any, + } as never, }, ], }; diff --git a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts index 6f6eae006..8be7538df 100644 --- a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.test.ts @@ -2,8 +2,8 @@ import { LeagueConfigPresenter } from './LeagueConfigPresenter'; describe('LeagueConfigPresenter', () => { - const createFullConfig = (overrides: Partial = {}): any => { - const base: any = { + const createFullConfig = (overrides: Partial = {}): unknown => { + const base: unknown = { // eslint-disable-next-line @typescript-eslint/no-explicit-any league: { id: 'league-1', @@ -12,7 +12,7 @@ describe('LeagueConfigPresenter', () => { ownerId: 'owner-1', settings: { pointsSystem: 'custom' }, createdAt: new Date(), - } as any, + } as never, activeSeason: { id: 'season-1', leagueId: 'league-1', @@ -21,9 +21,9 @@ describe('LeagueConfigPresenter', () => { status: 'planned', schedule: { startDate: new Date('2025-01-05T19:00:00Z'), - timeOfDay: { hour: 20, minute: 0 } as any, - } as any, - dropPolicy: { strategy: 'bestNResults', n: 3 } as any, + timeOfDay: { hour: 20, minute: 0 } as never, + } as never, + dropPolicy: { strategy: 'bestNResults', n: 3 } as never, stewardingConfig: { decisionMode: 'steward_vote', requiredVotes: 3, @@ -34,8 +34,8 @@ describe('LeagueConfigPresenter', () => { stewardingClosesHours: 72, notifyAccusedOnProtest: true, notifyOnVoteRequired: true, - } as any, - } as any, + } as never, + } as never, scoringConfig: { id: 'scoring-1', seasonId: 'season-1', @@ -43,17 +43,17 @@ describe('LeagueConfigPresenter', () => { { id: 'champ-1', name: 'Drivers', - type: 'driver' as any, - sessionTypes: ['race'] as any, + type: 'driver' as never, + sessionTypes: ['race'] as never, pointsTableBySessionType: { race: { getPointsForPosition: (pos: number) => (pos === 1 ? 25 : 0), - } as any, + } as never, }, - dropScorePolicy: { strategy: 'bestNResults', count: 3 } as any, + dropScorePolicy: { strategy: 'bestNResults', count: 3 } as never, }, ], - } as any, + } as never, game: undefined, ...overrides, }; @@ -65,7 +65,7 @@ describe('LeagueConfigPresenter', () => { const presenter = new LeagueConfigPresenter(); const fullConfig = createFullConfig(); - presenter.present({ config: fullConfig }); + presenter.present({ config: fullConfig } as never); const vm = presenter.getViewModel()!; expect(vm).not.toBeNull(); diff --git a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts index 2907c0ac8..313253ba9 100644 --- a/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts +++ b/apps/api/src/domain/league/presenters/LeagueConfigPresenter.ts @@ -10,8 +10,28 @@ export class LeagueConfigPresenter implements UseCaseOutputPort number }> }[] }; + }; const league = dto.league; const settings = league.settings; const stewarding = dto.activeSeason?.stewardingConfig; @@ -53,7 +73,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort { requestedAt: new Date('2023-01-01'), message: 'Please accept me', // eslint-disable-next-line @typescript-eslint/no-explicit-any - driver: { id: 'driver-1', name: 'John Doe' } as any, + driver: { id: 'driver-1', name: 'John Doe' } as never, }, ], }; diff --git a/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts b/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts index 7a6431a9e..c14d1e908 100644 --- a/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts +++ b/apps/api/src/domain/league/presenters/LeagueOwnerSummaryPresenter.test.ts @@ -6,7 +6,7 @@ describe('LeagueOwnerSummaryPresenter', () => { const presenter = new LeagueOwnerSummaryPresenter(); const output: GetLeagueOwnerSummaryResult = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - league: {} as any, + league: {} as never, // eslint-disable-next-line @typescript-eslint/no-explicit-any owner: { id: 'driver-1', @@ -14,9 +14,9 @@ describe('LeagueOwnerSummaryPresenter', () => { name: 'John Doe', country: 'US', bio: 'Racing enthusiast', - joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') } as any, + joinedAt: { toDate: () => new Date('2024-01-01T00:00:00Z') } as never, // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any, + } as never, totalMembers: 50, activeMembers: 45, rating: 1500, diff --git a/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.test.ts b/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.test.ts index aeab68250..34b1f3c4a 100644 --- a/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.test.ts +++ b/apps/api/src/domain/league/presenters/LeagueSchedulePresenter.test.ts @@ -19,23 +19,23 @@ describe('LeagueSchedulePresenter', () => { seasonId: 'season-1', published: false, races: [{ race }], - } as any); + } as never); - const vm = presenter.getViewModel() as any; + const vm = presenter.getViewModel() as unknown as { seasonId: string, published: boolean, races: { id: string, name: string, date: string }[] }; expect(vm).not.toBeNull(); expect(vm.seasonId).toBe('season-1'); expect(vm.published).toBe(false); expect(Array.isArray(vm.races)).toBe(true); - expect(vm.races[0]).toMatchObject({ + expect(vm.races[0]!).toMatchObject({ id: 'race-1', name: 'Spa - GT3', date: '2025-01-02T20:00:00.000Z', }); // Guard: dates must be ISO strings (no Date objects) - expect(typeof vm.races[0].date).toBe('string'); - expect(vm.races[0].date).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); + expect(typeof vm.races[0]!.date).toBe('string'); + expect(vm.races[0]!.date).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/); }); }); \ No newline at end of file diff --git a/apps/api/src/domain/media/DefaultAvatarAssets.http.test.ts b/apps/api/src/domain/media/DefaultAvatarAssets.http.test.ts index be26deeba..de2471bb5 100644 --- a/apps/api/src/domain/media/DefaultAvatarAssets.http.test.ts +++ b/apps/api/src/domain/media/DefaultAvatarAssets.http.test.ts @@ -9,7 +9,7 @@ describe('Default avatar assets (HTTP)', () => { const originalEnv = { ...process.env }; let module: TestingModule | undefined; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); diff --git a/apps/api/src/domain/media/MediaController.test.ts b/apps/api/src/domain/media/MediaController.test.ts index 2c21b5af6..9f1f1e3ee 100644 --- a/apps/api/src/domain/media/MediaController.test.ts +++ b/apps/api/src/domain/media/MediaController.test.ts @@ -35,6 +35,8 @@ describe('MediaController', () => { deleteMedia: ReturnType; getAvatar: ReturnType; updateAvatar: ReturnType; + getUploadedMedia: ReturnType; + resolveMediaReference: ReturnType; }; let generationService: MediaGenerationService & { generateDriverAvatar: ReturnType; @@ -57,6 +59,8 @@ describe('MediaController', () => { deleteMedia: vi.fn(), getAvatar: vi.fn(), updateAvatar: vi.fn(), + getUploadedMedia: vi.fn(), + resolveMediaReference: vi.fn(), }, }, { @@ -97,8 +101,8 @@ describe('MediaController', () => { }).compile(); controller = module.get(MediaController); - service = module.get(MediaService) as any; - generationService = module.get(MediaGenerationService) as any; + service = module.get(MediaService) as never; + generationService = module.get(MediaGenerationService) as never; }); const createMockResponse = (): Response => { @@ -375,6 +379,7 @@ describe('MediaController', () => { }; const mockService = { getMedia: vi.fn().mockResolvedValue({ id: mediaId }), + getUploadedMedia: vi.fn().mockResolvedValue({ bytes: pngBuffer, contentType: 'image/png' }), }; const module = await Test.createTestingModule({ @@ -409,8 +414,7 @@ describe('MediaController', () => { await testController.getUploadedMedia(mediaId, res); expect(mockService.getMedia).toHaveBeenCalledWith(mediaId); - expect(mockStorage.getBytes).toHaveBeenCalledWith('uploaded/media-123'); - expect(mockStorage.getMetadata).toHaveBeenCalledWith('uploaded/media-123'); + expect(mockService.getUploadedMedia).toHaveBeenCalledWith('uploaded/media-123'); expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/png'); expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', 'public, max-age=31536000, immutable'); expect(res.status).toHaveBeenCalledWith(200); @@ -569,7 +573,7 @@ describe('MediaController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -603,6 +607,8 @@ describe('MediaController', () => { uploadMedia: vi.fn(), getAvatar: vi.fn(), updateAvatar: vi.fn(), + getUploadedMedia: vi.fn(async () => ({ bytes: Buffer.from([]), contentType: 'image/png' })), + resolveMediaReference: vi.fn(async () => '/path'), }, }, { @@ -657,9 +663,9 @@ describe('MediaController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/media/MediaController.ts b/apps/api/src/domain/media/MediaController.ts index 7c8eb2d3c..3157eb72f 100644 --- a/apps/api/src/domain/media/MediaController.ts +++ b/apps/api/src/domain/media/MediaController.ts @@ -19,9 +19,7 @@ import type { MulterFile } from './types/MulterFile'; import { MediaGenerationService } from '@core/media/domain/services/MediaGenerationService'; import type { Logger } from '@core/shared/application/Logger'; import { MediaReference } from '@core/domain/media/MediaReference'; -import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter'; -import { LOGGER_TOKEN, MEDIA_STORAGE_PORT_TOKEN } from './MediaTokens'; -import type { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort'; +import { LOGGER_TOKEN } from './MediaTokens'; import path from 'node:path'; import fs from 'node:fs/promises'; @@ -36,8 +34,6 @@ export class MediaController { @Inject(MediaService) private readonly mediaService: MediaService, @Inject(MediaGenerationService) private readonly mediaGenerationService: MediaGenerationService, @Inject(LOGGER_TOKEN) private readonly logger: Logger, - @Inject(MediaResolverAdapter) private readonly mediaResolver: MediaResolverAdapter, - @Inject(MEDIA_STORAGE_PORT_TOKEN) private readonly mediaStorage: MediaStoragePort, ) {} @Post('avatar/generate') @@ -272,23 +268,19 @@ export class MediaController { // The mediaId is used as the storage key const storageKey = `uploaded/${mediaId}`; - // Get file bytes from storage - const bytes = await this.mediaStorage.getBytes!(storageKey); - if (!bytes) { + // Get file bytes from storage via service + const result = await this.mediaService.getUploadedMedia(storageKey); + if (!result) { this.logger.warn('[MediaController] Uploaded media file not found', { mediaId, storageKey }); res.status(HttpStatus.NOT_FOUND).json({ error: 'Media file not found' }); return; } - // Get metadata to determine content type - const metadata = await this.mediaStorage.getMetadata!(storageKey); - const contentType = metadata?.contentType || 'application/octet-stream'; - - res.setHeader('Content-Type', contentType); + res.setHeader('Content-Type', result.contentType); res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); - res.status(HttpStatus.OK).send(bytes); + res.status(HttpStatus.OK).send(result.bytes); - this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: bytes.length }); + this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: result.bytes.length }); } @Public() @@ -339,7 +331,7 @@ export class MediaController { if (ref) { refHash = ref.hash(); - resolvedPath = await this.mediaResolver.resolve(ref); + resolvedPath = await this.mediaService.resolveMediaReference(ref); if (!resolvedPath) { notes.push('Resolver returned null'); @@ -382,7 +374,7 @@ export class MediaController { ): Promise { this.logger.debug('[MediaController] Getting media details', { mediaId }); const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId); - + if (dto) { this.logger.info('[MediaController] Media details found', { mediaId }); res.status(HttpStatus.OK).json(dto); diff --git a/apps/api/src/domain/media/MediaProviders.ts b/apps/api/src/domain/media/MediaProviders.ts index 9b3c8643c..c5786dc2a 100644 --- a/apps/api/src/domain/media/MediaProviders.ts +++ b/apps/api/src/domain/media/MediaProviders.ts @@ -8,6 +8,7 @@ import { FaceValidationPort } from '@core/media/application/ports/FaceValidation import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort'; import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort'; import type { Logger } from '@core/shared/application'; +import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort'; // Import use cases import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase'; @@ -16,6 +17,8 @@ import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCa import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase'; import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase'; import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase'; +import { ResolveMediaReferenceUseCase } from '@core/media/application/use-cases/ResolveMediaReferenceUseCase'; +import { GetUploadedMediaUseCase } from '@core/media/application/use-cases/GetUploadedMediaUseCase'; // Import presenters import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter'; @@ -39,6 +42,9 @@ import { DELETE_MEDIA_USE_CASE_TOKEN, GET_AVATAR_USE_CASE_TOKEN, UPDATE_AVATAR_USE_CASE_TOKEN, + RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN, + GET_UPLOADED_MEDIA_USE_CASE_TOKEN, + MEDIA_RESOLVER_PORT_TOKEN, } from './MediaTokens'; export * from './MediaTokens'; @@ -92,7 +98,7 @@ const initLogger = InitializationLogger.getInstance(); export const MediaProviders: Provider[] = createLoggedProviders([ MediaGenerationService, { - provide: MediaResolverAdapter, + provide: MEDIA_RESOLVER_PORT_TOKEN, useFactory: () => new MediaResolverAdapter({}), }, RequestAvatarGenerationPresenter, @@ -156,4 +162,16 @@ export const MediaProviders: Provider[] = createLoggedProviders([ new UpdateAvatarUseCase(avatarRepo, logger), inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN], }, -], initLogger); \ No newline at end of file + { + 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); diff --git a/apps/api/src/domain/media/MediaService.test.ts b/apps/api/src/domain/media/MediaService.test.ts index 98f2e6e5c..2f2dd169f 100644 --- a/apps/api/src/domain/media/MediaService.test.ts +++ b/apps/api/src/domain/media/MediaService.test.ts @@ -16,28 +16,30 @@ describe('MediaService', () => { }; const service = new MediaService( - requestAvatarGenerationUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - requestAvatarGenerationPresenter as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + requestAvatarGenerationUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + requestAvatarGenerationPresenter as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect( - service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as any, suitColor: 'red' as any }), + service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as never, suitColor: 'red' as never }), ).resolves.toEqual({ success: true, requestId: 'r1', avatarUrls: ['u1'], errorMessage: '' }); expect(requestAvatarGenerationUseCase.execute).toHaveBeenCalledWith({ userId: 'u1', - facePhotoData: {} as any, + facePhotoData: {} as never, suitColor: 'red', }); expect(requestAvatarGenerationPresenter.transform).toHaveBeenCalledWith({ requestId: 'r1', status: 'completed', avatarUrls: ['u1'] }); @@ -45,23 +47,25 @@ describe('MediaService', () => { it('requestAvatarGeneration returns failure DTO on error', async () => { const service = new MediaService( - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'fail' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: { success: true } } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'fail' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: { success: true } } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect( - service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as any, suitColor: 'red' as any }), + service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as never, suitColor: 'red' as never }), ).resolves.toEqual({ success: false, requestId: '', @@ -78,30 +82,32 @@ describe('MediaService', () => { const uploadMediaUseCase = { execute: vi.fn(async () => Result.ok({ mediaId: 'm1', url: 'https://example.com/m1.png' })) }; const service = new MediaService( - { execute: vi.fn() } as any, - uploadMediaUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - uploadMediaPresenter as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + uploadMediaUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + uploadMediaPresenter as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect( - service.uploadMedia({ file: {} as any, userId: 'u1', metadata: { a: 1 } } as any), + service.uploadMedia({ file: {} as never, userId: 'u1', metadata: { a: 1 } } as never), ).resolves.toEqual({ success: true, mediaId: 'm1', }); expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ - file: {} as any, + file: {} as never, uploadedBy: 'u1', metadata: { a: 1 }, }); @@ -116,23 +122,25 @@ describe('MediaService', () => { }; const service = new MediaService( - { execute: vi.fn() } as any, - uploadMediaUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - uploadMediaPresenter as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + uploadMediaUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + uploadMediaPresenter as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); - await expect(service.uploadMedia({ file: {} as any } as any)).resolves.toEqual({ success: true, mediaId: 'm1' }); - expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as any, uploadedBy: '', metadata: {} }); + await expect(service.uploadMedia({ file: {} as never } as never)).resolves.toEqual({ success: true, mediaId: 'm1' }); + expect(uploadMediaUseCase.execute).toHaveBeenCalledWith({ file: {} as never, uploadedBy: '', metadata: {} }); expect(uploadMediaPresenter.transform).toHaveBeenCalledWith({ mediaId: 'm1', url: 'https://example.com/m1.png' }); }); @@ -142,22 +150,24 @@ describe('MediaService', () => { }; const service = new MediaService( - { execute: vi.fn() } as any, - uploadMediaUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: { success: true } } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + uploadMediaUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: { success: true } } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); - await expect(service.uploadMedia({ file: {} as any, userId: 'u1' } as any)).resolves.toEqual({ + await expect(service.uploadMedia({ file: {} as never, userId: 'u1' } as never)).resolves.toEqual({ success: false, error: 'nope', }); @@ -172,19 +182,21 @@ describe('MediaService', () => { }; const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - getMediaUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - getMediaPresenter as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + getMediaUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + getMediaPresenter as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); const result = await service.getMedia('m1'); @@ -195,19 +207,21 @@ describe('MediaService', () => { it('getMedia returns null on MEDIA_NOT_FOUND', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'MEDIA_NOT_FOUND', details: { message: 'n/a' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'MEDIA_NOT_FOUND', details: { message: 'n/a' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.getMedia('m1')).resolves.toBeNull(); @@ -215,19 +229,21 @@ describe('MediaService', () => { it('getMedia throws on non-not-found error', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.getMedia('m1')).rejects.toThrow('boom'); @@ -241,19 +257,21 @@ describe('MediaService', () => { }; const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - deleteMediaUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - deleteMediaPresenter as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + deleteMediaUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + deleteMediaPresenter as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.deleteMedia('m1')).resolves.toEqual({ success: true }); @@ -263,19 +281,21 @@ describe('MediaService', () => { it('deleteMedia returns failure DTO on error', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: { success: true } } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: { success: true } } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.deleteMedia('m1')).resolves.toEqual({ success: false, error: 'nope' }); @@ -290,19 +310,21 @@ describe('MediaService', () => { }; const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - getAvatarUseCase as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - getAvatarPresenter as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + getAvatarUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + getAvatarPresenter as never, + { responseModel: {} } as never, ); await expect(service.getAvatar('d1')).resolves.toEqual({ avatarUrl: 'https://example.com/avatar.png' }); @@ -312,19 +334,21 @@ describe('MediaService', () => { it('getAvatar returns null on AVATAR_NOT_FOUND', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'AVATAR_NOT_FOUND', details: { message: 'n/a' } })) } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'AVATAR_NOT_FOUND', details: { message: 'n/a' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.getAvatar('d1')).resolves.toBeNull(); @@ -332,19 +356,21 @@ describe('MediaService', () => { it('getAvatar throws on non-not-found error', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.getAvatar('d1')).rejects.toThrow('boom'); @@ -358,44 +384,48 @@ describe('MediaService', () => { }; const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - updateAvatarUseCase as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - updateAvatarPresenter as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + updateAvatarUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + updateAvatarPresenter as never, ); - await expect(service.updateAvatar('d1', { avatarUrl: 'u1' } as any)).resolves.toEqual({ success: true }); + await expect(service.updateAvatar('d1', { avatarUrl: 'u1' } as never)).resolves.toEqual({ success: true }); expect(updateAvatarUseCase.execute).toHaveBeenCalledWith({ driverId: 'd1', mediaUrl: 'u1' }); expect(updateAvatarPresenter.transform).toHaveBeenCalledWith({ avatarId: 'a1', driverId: 'd1' }); }); it('updateAvatar returns failure DTO on error', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: { success: true } } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'nope' } })) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: { success: true } } as never, ); - await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as any)).resolves.toEqual({ + await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as never)).resolves.toEqual({ success: false, error: 'nope', }); @@ -403,23 +433,25 @@ describe('MediaService', () => { it('requestAvatarGeneration uses fallback errorMessage when no details.message', async () => { const service = new MediaService( - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: { success: true } } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: { success: true } } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect( - service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as any, suitColor: 'red' as any }), + service.requestAvatarGeneration({ userId: 'u1', facePhotoData: {} as never, suitColor: 'red' as never }), ).resolves.toEqual({ success: false, requestId: '', @@ -430,26 +462,28 @@ describe('MediaService', () => { it('uploadMedia uses fallback error when no details.message', async () => { const uploadMediaUseCase = { - execute: vi.fn(async () => Result.err({ code: 'UPLOAD_FAILED' } as any)), + execute: vi.fn(async () => Result.err({ code: 'UPLOAD_FAILED' } as never)), }; const service = new MediaService( - { execute: vi.fn() } as any, - uploadMediaUseCase as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: { success: true } } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + uploadMediaUseCase as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: { success: true } } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); - await expect(service.uploadMedia({ file: {} as any, userId: 'u1' } as any)).resolves.toEqual({ + await expect(service.uploadMedia({ file: {} as never, userId: 'u1' } as never)).resolves.toEqual({ success: false, error: 'Upload failed', }); @@ -457,19 +491,21 @@ describe('MediaService', () => { it('getMedia throws fallback message when no details.message and not not-found', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.getMedia('m1')).rejects.toThrow('Failed to get media'); @@ -477,19 +513,21 @@ describe('MediaService', () => { it('deleteMedia uses fallback message when no details.message', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: { success: true } } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: { success: true } } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.deleteMedia('m1')).resolves.toEqual({ success: false, error: 'Failed to delete media' }); @@ -497,19 +535,21 @@ describe('MediaService', () => { it('getAvatar throws fallback message when no details.message and not not-found', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - { execute: vi.fn() } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, ); await expect(service.getAvatar('d1')).rejects.toThrow('Failed to get avatar'); @@ -517,24 +557,26 @@ describe('MediaService', () => { it('updateAvatar uses fallback message when no details.message', async () => { const service = new MediaService( - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn() } as any, - { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) } as any, - logger as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: {} } as any, - { responseModel: { success: true } } as any, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) } as never, + { execute: vi.fn() } as never, + { execute: vi.fn() } as never, + logger as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: {} } as never, + { responseModel: { success: true } } as never, ); - await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as any)).resolves.toEqual({ + await expect(service.updateAvatar('d1', { avatarUrl: 'u' } as never)).resolves.toEqual({ success: false, error: 'Failed to update avatar', }); }); -}); \ No newline at end of file +}); diff --git a/apps/api/src/domain/media/MediaService.ts b/apps/api/src/domain/media/MediaService.ts index bcdd41e90..3db91890e 100644 --- a/apps/api/src/domain/media/MediaService.ts +++ b/apps/api/src/domain/media/MediaService.ts @@ -12,6 +12,7 @@ import type { ValidateFaceInputDTO } from './dtos/ValidateFaceInputDTO'; import type { ValidateFaceOutputDTO } from './dtos/ValidateFaceOutputDTO'; import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest'; import type { MulterFile } from './types/MulterFile'; +import type { MediaReference } from '@core/domain/media/MediaReference'; type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO; type UploadMediaInput = UploadMediaInputDTO; @@ -24,6 +25,8 @@ import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCa import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase'; import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase'; import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase'; +import { ResolveMediaReferenceUseCase } from '@core/media/application/use-cases/ResolveMediaReferenceUseCase'; +import { GetUploadedMediaUseCase, type GetUploadedMediaResult } from '@core/media/application/use-cases/GetUploadedMediaUseCase'; // Presenters (now transformers) import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter'; @@ -40,6 +43,8 @@ import { DELETE_MEDIA_USE_CASE_TOKEN, GET_AVATAR_USE_CASE_TOKEN, UPDATE_AVATAR_USE_CASE_TOKEN, + RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN, + GET_UPLOADED_MEDIA_USE_CASE_TOKEN, LOGGER_TOKEN, } from './MediaTokens'; import type { Logger } from '@core/shared/application'; @@ -59,6 +64,10 @@ export class MediaService { private readonly getAvatarUseCase: GetAvatarUseCase, @Inject(UPDATE_AVATAR_USE_CASE_TOKEN) private readonly updateAvatarUseCase: UpdateAvatarUseCase, + @Inject(RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN) + private readonly resolveMediaReferenceUseCase: ResolveMediaReferenceUseCase, + @Inject(GET_UPLOADED_MEDIA_USE_CASE_TOKEN) + private readonly getUploadedMediaUseCase: GetUploadedMediaUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, private readonly requestAvatarGenerationPresenter: RequestAvatarGenerationPresenter, @@ -211,4 +220,20 @@ export class MediaService { return { isValid: true }; } -} \ No newline at end of file + + async resolveMediaReference(reference: MediaReference): Promise { + const result = await this.resolveMediaReferenceUseCase.execute({ reference }); + if (result.isErr()) { + throw new Error(result.unwrapErr().message); + } + return result.unwrap(); + } + + async getUploadedMedia(storageKey: string): Promise { + const result = await this.getUploadedMediaUseCase.execute({ storageKey }); + if (result.isErr()) { + throw new Error(result.unwrapErr().message); + } + return result.unwrap(); + } +} diff --git a/apps/api/src/domain/media/MediaTokens.ts b/apps/api/src/domain/media/MediaTokens.ts index 17f0c2197..17935c956 100644 --- a/apps/api/src/domain/media/MediaTokens.ts +++ b/apps/api/src/domain/media/MediaTokens.ts @@ -11,4 +11,7 @@ export const UPLOAD_MEDIA_USE_CASE_TOKEN = 'UploadMediaUseCase'; export const GET_MEDIA_USE_CASE_TOKEN = 'GetMediaUseCase'; export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase'; export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase'; -export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase'; \ No newline at end of file +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'; \ No newline at end of file diff --git a/apps/api/src/domain/notifications/NotificationsModule.ts b/apps/api/src/domain/notifications/NotificationsModule.ts index 44ed4e4a3..e2b42e9e6 100644 --- a/apps/api/src/domain/notifications/NotificationsModule.ts +++ b/apps/api/src/domain/notifications/NotificationsModule.ts @@ -3,11 +3,12 @@ import { Module } from '@nestjs/common'; import { NotificationsPersistenceModule } from '../../persistence/notifications/NotificationsPersistenceModule'; import { NotificationsController } from './NotificationsController'; import { NotificationsService } from './NotificationsService'; +import { NotificationsProviders } from './NotificationsProviders'; @Module({ imports: [NotificationsPersistenceModule], controllers: [NotificationsController], - providers: [NotificationsService], + providers: [NotificationsService, ...NotificationsProviders], exports: [NotificationsPersistenceModule, NotificationsService], }) -export class NotificationsModule {} \ No newline at end of file +export class NotificationsModule {} diff --git a/apps/api/src/domain/notifications/NotificationsProviders.ts b/apps/api/src/domain/notifications/NotificationsProviders.ts new file mode 100644 index 000000000..26898c11b --- /dev/null +++ b/apps/api/src/domain/notifications/NotificationsProviders.ts @@ -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], + }, +]; diff --git a/apps/api/src/domain/notifications/NotificationsService.ts b/apps/api/src/domain/notifications/NotificationsService.ts index 5e031a08a..4138060ef 100644 --- a/apps/api/src/domain/notifications/NotificationsService.ts +++ b/apps/api/src/domain/notifications/NotificationsService.ts @@ -1,35 +1,48 @@ import { Inject, Injectable } from '@nestjs/common'; -import type { INotificationRepository } from '@core/notifications/domain/repositories/INotificationRepository'; -import { NOTIFICATION_REPOSITORY_TOKEN } from '../../persistence/notifications/NotificationsPersistenceTokens'; +import { GetUnreadNotificationsUseCase } from '@core/notifications/application/use-cases/GetUnreadNotificationsUseCase'; +import { GetAllNotificationsUseCase } from '@core/notifications/application/use-cases/GetAllNotificationsUseCase'; +import { MarkNotificationReadUseCase } from '@core/notifications/application/use-cases/MarkNotificationReadUseCase'; +import { + GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN, + GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN, + MARK_NOTIFICATION_READ_USE_CASE_TOKEN, +} from './NotificationsTokens'; @Injectable() export class NotificationsService { constructor( - @Inject(NOTIFICATION_REPOSITORY_TOKEN) - private readonly notificationRepository: INotificationRepository, + @Inject(GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN) + private readonly getUnreadNotificationsUseCase: GetUnreadNotificationsUseCase, + @Inject(GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN) + private readonly getAllNotificationsUseCase: GetAllNotificationsUseCase, + @Inject(MARK_NOTIFICATION_READ_USE_CASE_TOKEN) + private readonly markNotificationReadUseCase: MarkNotificationReadUseCase, ) {} async getUnreadNotifications(userId: string): Promise>> { - const notifications = await this.notificationRepository.findUnreadByRecipientId(userId); - return notifications.map(n => n.toJSON() as Record); + const result = await this.getUnreadNotificationsUseCase.execute({ recipientId: userId }); + if (result.isErr()) { + throw new Error(result.unwrapErr().details?.message || 'Failed to get unread notifications'); + } + return result.unwrap().notifications.map(n => n.toJSON() as Record); } async getAllNotifications(userId: string): Promise>> { - const notifications = await this.notificationRepository.findByRecipientId(userId); - return notifications.map(n => n.toJSON() as Record); + const result = await this.getAllNotificationsUseCase.execute({ recipientId: userId }); + if (result.isErr()) { + throw new Error(result.unwrapErr().details?.message || 'Failed to get all notifications'); + } + return result.unwrap().notifications.map(n => n.toJSON() as Record); } async markAsRead(notificationId: string, userId: string): Promise { - const notification = await this.notificationRepository.findById(notificationId); - if (!notification) { - throw new Error('Notification not found'); - } + const result = await this.markNotificationReadUseCase.execute({ + notificationId, + recipientId: userId, + }); - if (notification.recipientId !== userId) { - throw new Error('Unauthorized'); + if (result.isErr()) { + throw new Error(result.unwrapErr().details?.message || 'Failed to mark notification as read'); } - - const updated = notification.markAsRead(); - await this.notificationRepository.update(updated); } } diff --git a/apps/api/src/domain/notifications/NotificationsTokens.ts b/apps/api/src/domain/notifications/NotificationsTokens.ts new file mode 100644 index 000000000..f688623f8 --- /dev/null +++ b/apps/api/src/domain/notifications/NotificationsTokens.ts @@ -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'); diff --git a/apps/api/src/domain/payments/Payments.http.test.ts b/apps/api/src/domain/payments/Payments.http.test.ts index 0e561c382..3df66656d 100644 --- a/apps/api/src/domain/payments/Payments.http.test.ts +++ b/apps/api/src/domain/payments/Payments.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Payments domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Payments domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/payments/PaymentsController.test.ts b/apps/api/src/domain/payments/PaymentsController.test.ts index a532288d1..c52c0642d 100644 --- a/apps/api/src/domain/payments/PaymentsController.test.ts +++ b/apps/api/src/domain/payments/PaymentsController.test.ts @@ -377,7 +377,7 @@ describe('PaymentsController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -415,9 +415,9 @@ describe('PaymentsController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/payments/PaymentsService.test.ts b/apps/api/src/domain/payments/PaymentsService.test.ts index ffdd84c66..d0faea02c 100644 --- a/apps/api/src/domain/payments/PaymentsService.test.ts +++ b/apps/api/src/domain/payments/PaymentsService.test.ts @@ -5,7 +5,9 @@ import { PaymentsService } from './PaymentsService'; describe('PaymentsService', () => { const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }; - function makeService(overrides?: Partial>) { + type MockUseCase = { execute: ReturnType }; + + function makeService(overrides?: Partial>) { const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) }; const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) }; const updatePaymentStatusUseCase = @@ -25,19 +27,19 @@ describe('PaymentsService', () => { overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) }; const service = new PaymentsService( - getPaymentsUseCase as any, - createPaymentUseCase as any, - updatePaymentStatusUseCase as any, - getMembershipFeesUseCase as any, - upsertMembershipFeeUseCase as any, - updateMemberPaymentUseCase as any, - getPrizesUseCase as any, - createPrizeUseCase as any, - awardPrizeUseCase as any, - deletePrizeUseCase as any, - getWalletUseCase as any, - processWalletTransactionUseCase as any, - logger as any, + getPaymentsUseCase as never, + createPaymentUseCase as never, + updatePaymentStatusUseCase as never, + getMembershipFeesUseCase as never, + upsertMembershipFeeUseCase as never, + updateMemberPaymentUseCase as never, + getPrizesUseCase as never, + createPrizeUseCase as never, + awardPrizeUseCase as never, + deletePrizeUseCase as never, + getWalletUseCase as never, + processWalletTransactionUseCase as never, + logger as never, ); return { @@ -59,7 +61,7 @@ describe('PaymentsService', () => { it('getPayments returns presenter model on success', async () => { const { service, getPaymentsUseCase } = makeService(); - await expect(service.getPayments({ leagueId: 'l1' } as any)).resolves.toEqual({ payments: [] }); + await expect(service.getPayments({ leagueId: 'l1' } as never)).resolves.toEqual({ payments: [] }); expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); @@ -67,12 +69,12 @@ describe('PaymentsService', () => { const { service } = makeService({ getPaymentsUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, }); - await expect(service.getPayments({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR'); + await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR'); }); it('createPayment returns presenter model on success', async () => { const { service, createPaymentUseCase } = makeService(); - await expect(service.createPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ paymentId: 'p1' }); + await expect(service.createPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ paymentId: 'p1' }); expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); @@ -80,12 +82,12 @@ describe('PaymentsService', () => { const { service } = makeService({ createPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, }); - await expect(service.createPayment({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR'); + await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR'); }); it('updatePaymentStatus returns presenter model on success', async () => { const { service, updatePaymentStatusUseCase } = makeService(); - await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).resolves.toEqual({ success: true }); + await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).resolves.toEqual({ success: true }); expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' }); }); @@ -93,7 +95,7 @@ describe('PaymentsService', () => { const { service } = makeService({ updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, }); - await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).rejects.toThrow('REPOSITORY_ERROR'); + await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('REPOSITORY_ERROR'); }); it('getMembershipFees returns viewModel on success', async () => { @@ -101,7 +103,7 @@ describe('PaymentsService', () => { getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) } }); - await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).resolves.toEqual({ + await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).resolves.toEqual({ fee: { amount: 1 }, payments: [], }); @@ -112,22 +114,22 @@ describe('PaymentsService', () => { const { service } = makeService({ getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, }); - await expect(service.getMembershipFees({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR'); + await expect(service.getMembershipFees({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR'); }); it('upsertMembershipFee returns viewModel on success', async () => { const { service, upsertMembershipFeeUseCase } = makeService(); - await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true }); + await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true }); expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); it('upsertMembershipFee throws on error branch (defensive check)', async () => { const { service } = makeService({ - upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) }, + upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) }, }); - await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).rejects.toThrow( + await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).rejects.toThrow( 'Failed to upsert membership fee', ); }); @@ -135,7 +137,7 @@ describe('PaymentsService', () => { it('updateMemberPayment returns viewModel on success', async () => { const { service, updateMemberPaymentUseCase } = makeService(); - await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true }); + await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true }); expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); @@ -144,7 +146,7 @@ describe('PaymentsService', () => { updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) }, }); - await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR'); + await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR'); }); it('getPrizes maps seasonId optional', async () => { @@ -153,10 +155,10 @@ describe('PaymentsService', () => { getPrizesUseCase, }); - await expect(service.getPrizes({ leagueId: 'l1' } as any)).resolves.toEqual({ prizes: [] }); + await expect(service.getPrizes({ leagueId: 'l1' } as never)).resolves.toEqual({ prizes: [] }); expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); - await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as any)).resolves.toEqual({ prizes: [] }); + await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as never)).resolves.toEqual({ prizes: [] }); expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 's1' }); }); @@ -166,7 +168,7 @@ describe('PaymentsService', () => { createPrizeUseCase, }); - await expect(service.createPrize({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true }); + await expect(service.createPrize({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true }); expect(createPrizeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); @@ -176,7 +178,7 @@ describe('PaymentsService', () => { awardPrizeUseCase, }); - await expect(service.awardPrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true }); + await expect(service.awardPrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true }); expect(awardPrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' }); }); @@ -186,7 +188,7 @@ describe('PaymentsService', () => { deletePrizeUseCase, }); - await expect(service.deletePrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true }); + await expect(service.deletePrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true }); expect(deletePrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' }); }); @@ -196,7 +198,7 @@ describe('PaymentsService', () => { getWalletUseCase, }); - await expect(service.getWallet({ leagueId: 'l1' } as any)).resolves.toEqual({ balance: 10 }); + await expect(service.getWallet({ leagueId: 'l1' } as never)).resolves.toEqual({ balance: 10 }); expect(getWalletUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); @@ -206,47 +208,47 @@ describe('PaymentsService', () => { processWalletTransactionUseCase, }); - await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true }); + await expect(service.processWalletTransaction({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true }); expect(processWalletTransactionUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' }); }); it('getPayments throws fallback message when error has no code', async () => { const { service } = makeService({ - getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as any)) }, + getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as never)) }, }); - await expect(service.getPayments({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to get payments'); + await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to get payments'); }); it('createPayment throws fallback message when error has no code', async () => { const { service } = makeService({ - createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as any)) }, + createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) }, }); - await expect(service.createPayment({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to create payment'); + await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to create payment'); }); it('updatePaymentStatus throws fallback message when error has no code', async () => { const { service } = makeService({ - updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as any)) }, + updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as never)) }, }); - await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).rejects.toThrow('Failed to update payment status'); + await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('Failed to update payment status'); }); it('getMembershipFees throws fallback message when error has no code', async () => { const { service } = makeService({ - getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as any)) }, + getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as never)) }, }); - await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).rejects.toThrow('Failed to get membership fees'); + await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).rejects.toThrow('Failed to get membership fees'); }); it('updateMemberPayment throws fallback message when error has no code', async () => { const { service } = makeService({ - updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as any)) }, + updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) }, }); - await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to update member payment'); + await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to update member payment'); }); }); diff --git a/apps/api/src/domain/payments/presenters/index.ts b/apps/api/src/domain/payments/presenters/index.ts deleted file mode 100644 index 65455ad76..000000000 --- a/apps/api/src/domain/payments/presenters/index.ts +++ /dev/null @@ -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'; \ No newline at end of file diff --git a/apps/api/src/domain/policy/Policy.http.test.ts b/apps/api/src/domain/policy/Policy.http.test.ts index 127a81ce6..fab876b2a 100644 --- a/apps/api/src/domain/policy/Policy.http.test.ts +++ b/apps/api/src/domain/policy/Policy.http.test.ts @@ -14,7 +14,7 @@ import { FeatureAvailabilityGuard } from './FeatureAvailabilityGuard'; describe('Policy domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -61,9 +61,9 @@ describe('Policy domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/protests/Protests.http.test.ts b/apps/api/src/domain/protests/Protests.http.test.ts index f31e909b3..41678d963 100644 --- a/apps/api/src/domain/protests/Protests.http.test.ts +++ b/apps/api/src/domain/protests/Protests.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Protests domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Protests domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/race/Race.http.test.ts b/apps/api/src/domain/race/Race.http.test.ts index 610007169..75121b0f4 100644 --- a/apps/api/src/domain/race/Race.http.test.ts +++ b/apps/api/src/domain/race/Race.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Race domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Race domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/race/RaceController.test.ts b/apps/api/src/domain/race/RaceController.test.ts index 3083871e0..e9916d9e9 100644 --- a/apps/api/src/domain/race/RaceController.test.ts +++ b/apps/api/src/domain/race/RaceController.test.ts @@ -58,8 +58,8 @@ describe('RaceController', () => { describe('getAllRaces', () => { it('should return all races view model', async () => { - const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as any; - service.getAllRaces.mockResolvedValue(mockPresenter); + const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as unknown as { viewModel: unknown }; + service.getAllRaces.mockResolvedValue(mockPresenter as never); const result = await controller.getAllRaces(); @@ -70,8 +70,8 @@ describe('RaceController', () => { describe('getTotalRaces', () => { it('should return total races count view model', async () => { - const mockPresenter = { viewModel: { totalRaces: 5 } } as any; - service.getTotalRaces.mockResolvedValue(mockPresenter); + const mockPresenter = { viewModel: { totalRaces: 5 } } as unknown as { viewModel: unknown }; + service.getTotalRaces.mockResolvedValue(mockPresenter as never); const result = await controller.getTotalRaces(); @@ -81,7 +81,7 @@ describe('RaceController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -120,9 +120,9 @@ describe('RaceController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/race/RaceService.test.ts b/apps/api/src/domain/race/RaceService.test.ts index ca717fc5a..616d14f28 100644 --- a/apps/api/src/domain/race/RaceService.test.ts +++ b/apps/api/src/domain/race/RaceService.test.ts @@ -1,11 +1,11 @@ -import { describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi, type Mock } from 'vitest'; import { RaceService } from './RaceService'; import { Result } from '@core/shared/application/Result'; describe('RaceService', () => { it('invokes each use case and returns the corresponding presenter', async () => { // Mock use cases to return Result.ok() - const mkUseCase = (resultValue: any = { success: true }) => ({ + const mkUseCase = (resultValue: unknown = { success: true }) => ({ execute: vi.fn(async () => Result.ok(resultValue)) }); @@ -32,51 +32,51 @@ describe('RaceService', () => { const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() }; - const getAllRacesPresenter = { present: vi.fn() } as any; - const getTotalRacesPresenter = { present: vi.fn() } as any; - const importRaceResultsApiPresenter = { present: vi.fn() } as any; - const raceDetailPresenter = { present: vi.fn() } as any; - const racesPageDataPresenter = { present: vi.fn() } as any; - const allRacesPageDataPresenter = { present: vi.fn() } as any; - const raceResultsDetailPresenter = { present: vi.fn() } as any; - const raceWithSOFPresenter = { present: vi.fn() } as any; - const raceProtestsPresenter = { present: vi.fn() } as any; - const racePenaltiesPresenter = { present: vi.fn() } as any; - const commandResultPresenter = { present: vi.fn() } as any; + const getAllRacesPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const getTotalRacesPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const importRaceResultsApiPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const raceDetailPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const racesPageDataPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const allRacesPageDataPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const raceResultsDetailPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const raceWithSOFPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const raceProtestsPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const racePenaltiesPresenter = { present: vi.fn() } as unknown as { present: Mock }; + const commandResultPresenter = { present: vi.fn() } as unknown as { present: Mock }; const service = new RaceService( - getAllRacesUseCase as any, - getTotalRacesUseCase as any, - importRaceResultsApiUseCase as any, - getRaceDetailUseCase as any, - getRacesPageDataUseCase as any, - getAllRacesPageDataUseCase as any, - getRaceResultsDetailUseCase as any, - getRaceWithSOFUseCase as any, - getRaceProtestsUseCase as any, - getRacePenaltiesUseCase as any, - registerForRaceUseCase as any, - withdrawFromRaceUseCase as any, - cancelRaceUseCase as any, - completeRaceUseCase as any, - fileProtestUseCase as any, - quickPenaltyUseCase as any, - applyPenaltyUseCase as any, - requestProtestDefenseUseCase as any, - reviewProtestUseCase as any, - reopenRaceUseCase as any, - logger as any, - getAllRacesPresenter, - getTotalRacesPresenter, - importRaceResultsApiPresenter, - raceDetailPresenter, - racesPageDataPresenter, - allRacesPageDataPresenter, - raceResultsDetailPresenter, - raceWithSOFPresenter, - raceProtestsPresenter, - racePenaltiesPresenter, - commandResultPresenter, + getAllRacesUseCase as never, + getTotalRacesUseCase as never, + importRaceResultsApiUseCase as never, + getRaceDetailUseCase as never, + getRacesPageDataUseCase as never, + getAllRacesPageDataUseCase as never, + getRaceResultsDetailUseCase as never, + getRaceWithSOFUseCase as never, + getRaceProtestsUseCase as never, + getRacePenaltiesUseCase as never, + registerForRaceUseCase as never, + withdrawFromRaceUseCase as never, + cancelRaceUseCase as never, + completeRaceUseCase as never, + fileProtestUseCase as never, + quickPenaltyUseCase as never, + applyPenaltyUseCase as never, + requestProtestDefenseUseCase as never, + reviewProtestUseCase as never, + reopenRaceUseCase as never, + logger as never, + getAllRacesPresenter as never, + getTotalRacesPresenter as never, + importRaceResultsApiPresenter as never, + raceDetailPresenter as never, + racesPageDataPresenter as never, + allRacesPageDataPresenter as never, + raceResultsDetailPresenter as never, + raceWithSOFPresenter as never, + raceProtestsPresenter as never, + racePenaltiesPresenter as never, + commandResultPresenter as never, ); expect(await service.getAllRaces()).toBe(getAllRacesPresenter); @@ -87,11 +87,11 @@ describe('RaceService', () => { expect(getTotalRacesUseCase.execute).toHaveBeenCalledWith({}); expect(getTotalRacesPresenter.present).toHaveBeenCalledWith({ totalRaces: 0 }); - expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as any)).toBe(importRaceResultsApiPresenter); + expect(await service.importRaceResults({ raceId: 'r1', resultsFileContent: 'x' } as never)).toBe(importRaceResultsApiPresenter); expect(importRaceResultsApiUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', resultsFileContent: 'x' }); expect(importRaceResultsApiPresenter.present).toHaveBeenCalledWith({ success: true, raceId: 'r1', driversProcessed: 0, resultsRecorded: 0, errors: [] }); - expect(await service.getRaceDetail({ raceId: 'r1' } as any)).toBe(raceDetailPresenter); + expect(await service.getRaceDetail({ raceId: 'r1' } as never)).toBe(raceDetailPresenter); expect(getRaceDetailUseCase.execute).toHaveBeenCalled(); expect(await service.getRacesPageData('l1')).toBe(racesPageDataPresenter); @@ -118,43 +118,43 @@ describe('RaceService', () => { expect(getRacePenaltiesUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' }); expect(racePenaltiesPresenter.present).toHaveBeenCalledWith({ penalties: [], drivers: [] }); - expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter); + expect(await service.registerForRace({ raceId: 'r1', driverId: 'd1' } as never)).toBe(commandResultPresenter); expect(registerForRaceUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as any)).toBe(commandResultPresenter); + expect(await service.withdrawFromRace({ raceId: 'r1', driverId: 'd1' } as never)).toBe(commandResultPresenter); expect(withdrawFromRaceUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.cancelRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter); + expect(await service.cancelRace({ raceId: 'r1' } as never, 'admin')).toBe(commandResultPresenter); expect(cancelRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', cancelledById: 'admin' }); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.completeRace({ raceId: 'r1' } as any)).toBe(commandResultPresenter); + expect(await service.completeRace({ raceId: 'r1' } as never)).toBe(commandResultPresenter); expect(completeRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1' }); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.reopenRace({ raceId: 'r1' } as any, 'admin')).toBe(commandResultPresenter); + expect(await service.reopenRace({ raceId: 'r1' } as never, 'admin')).toBe(commandResultPresenter); expect(reopenRaceUseCase.execute).toHaveBeenCalledWith({ raceId: 'r1', reopenedById: 'admin' }); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.fileProtest({} as any)).toBe(commandResultPresenter); + expect(await service.fileProtest({} as never)).toBe(commandResultPresenter); expect(fileProtestUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.applyQuickPenalty({} as any)).toBe(commandResultPresenter); + expect(await service.applyQuickPenalty({} as never)).toBe(commandResultPresenter); expect(quickPenaltyUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.applyPenalty({} as any)).toBe(commandResultPresenter); + expect(await service.applyPenalty({} as never)).toBe(commandResultPresenter); expect(applyPenaltyUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.requestProtestDefense({} as any)).toBe(commandResultPresenter); + expect(await service.requestProtestDefense({} as never)).toBe(commandResultPresenter); expect(requestProtestDefenseUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); - expect(await service.reviewProtest({} as any)).toBe(commandResultPresenter); + expect(await service.reviewProtest({} as never)).toBe(commandResultPresenter); expect(reviewProtestUseCase.execute).toHaveBeenCalled(); expect(commandResultPresenter.present).toHaveBeenCalled(); }); diff --git a/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts index e68ed96b7..136e67180 100644 --- a/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts +++ b/apps/api/src/domain/race/presenters/GetAllRacesPresenter.test.ts @@ -35,13 +35,13 @@ describe('GetAllRacesPresenter', () => { strengthOfField: 1800, }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - ] as any, + ] as never, // eslint-disable-next-line @typescript-eslint/no-explicit-any leagues: [ { id: 'league-1', name: 'League One' }, { id: 'league-2', name: 'League Two' }, // eslint-disable-next-line @typescript-eslint/no-explicit-any - ] as any, + ] as never, totalCount: 3, }; @@ -66,9 +66,9 @@ describe('GetAllRacesPresenter', () => { const output: GetAllRacesResult = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - races: [] as any, + races: [] as never, // eslint-disable-next-line @typescript-eslint/no-explicit-any - leagues: [] as any, + leagues: [] as never, totalCount: 0, }; diff --git a/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.test.ts b/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.test.ts index ae1f6715b..d540dc577 100644 --- a/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.test.ts +++ b/apps/api/src/domain/race/presenters/RacePenaltiesPresenter.test.ts @@ -23,12 +23,12 @@ describe('RacePenaltiesPresenter', () => { const mockDriver = { id: 'driver-456', - name: { toString: () => 'John Doe' } as any, + name: { toString: () => 'John Doe' } as never, }; const result: GetRacePenaltiesResult = { - penalties: [mockPenalty as any], - drivers: [mockDriver as any], + penalties: [mockPenalty as never], + drivers: [mockDriver as never], }; presenter.present(result); @@ -63,7 +63,7 @@ describe('RacePenaltiesPresenter', () => { reason: 'Reason 1', issuedBy: 'steward-1', issuedAt: new Date('2024-01-01T10:00:00Z'), - } as any, + } as never, { id: 'penalty-2', driverId: 'driver-2', @@ -72,11 +72,11 @@ describe('RacePenaltiesPresenter', () => { reason: 'Reason 2', issuedBy: 'steward-1', issuedAt: new Date('2024-01-01T10:05:00Z'), - } as any, + } as never, ], drivers: [ - { id: 'driver-1', name: { toString: () => 'Driver One' } } as any, - { id: 'driver-2', name: { toString: () => 'Driver Two' } } as any, + { id: 'driver-1', name: { toString: () => 'Driver One' } } as never, + { id: 'driver-2', name: { toString: () => 'Driver Two' } } as never, ], }; @@ -114,9 +114,9 @@ describe('RacePenaltiesPresenter', () => { reason: 'Reason', issuedBy: 'steward-1', issuedAt: new Date('2024-01-01T10:00:00Z'), - } as any, + } as never, ], - drivers: [{ id: 'driver-1', name: { toString: () => 'Driver One' } } as any], + drivers: [{ id: 'driver-1', name: { toString: () => 'Driver One' } } as never], }; presenter.present(result); @@ -128,8 +128,8 @@ describe('RacePenaltiesPresenter', () => { describe('reset', () => { it('should clear the model', () => { presenter.present({ - penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any], - drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any], + penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as never], + drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never], }); expect(presenter.responseModel).toBeDefined(); @@ -147,8 +147,8 @@ describe('RacePenaltiesPresenter', () => { describe('viewModel', () => { it('should return same as responseModel', () => { const result: GetRacePenaltiesResult = { - penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as any], - drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any], + penalties: [{ id: 'p1', driverId: 'd1', type: 't', value: 1, reason: 'r', issuedBy: 's1', issuedAt: new Date() } as never], + drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never], }; presenter.present(result); diff --git a/apps/api/src/domain/race/presenters/RaceProtestsPresenter.test.ts b/apps/api/src/domain/race/presenters/RaceProtestsPresenter.test.ts index 3473403c3..ad8aee2de 100644 --- a/apps/api/src/domain/race/presenters/RaceProtestsPresenter.test.ts +++ b/apps/api/src/domain/race/presenters/RaceProtestsPresenter.test.ts @@ -15,26 +15,26 @@ describe('RaceProtestsPresenter', () => { protestingDriverId: 'driver-456', accusedDriverId: 'driver-789', incident: { - lap: { toNumber: () => 5 } as any, - description: { toString: () => 'Contact at turn 3' } as any, + lap: { toNumber: () => 5 } as never, + description: { toString: () => 'Contact at turn 3' } as never, }, - status: { toString: () => 'pending' } as any, + status: { toString: () => 'pending' } as never, filedAt: new Date('2024-01-01T10:30:00Z'), }; const mockDriver1 = { id: 'driver-456', - name: { toString: () => 'John Doe' } as any, + name: { toString: () => 'John Doe' } as never, }; const mockDriver2 = { id: 'driver-789', - name: { toString: () => 'Jane Smith' } as any, + name: { toString: () => 'Jane Smith' } as never, }; const result: GetRaceProtestsResult = { - protests: [mockProtest as any], - drivers: [mockDriver1 as any, mockDriver2 as any], + protests: [mockProtest as never], + drivers: [mockDriver1 as never, mockDriver2 as never], }; presenter.present(result); @@ -68,28 +68,28 @@ describe('RaceProtestsPresenter', () => { protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', incident: { - lap: { toNumber: () => 3 } as any, - description: { toString: () => 'Incident 1' } as any + lap: { toNumber: () => 3 } as never, + description: { toString: () => 'Incident 1' } as never }, - status: { toString: () => 'pending' } as any, + status: { toString: () => 'pending' } as never, filedAt: new Date('2024-01-01T10:00:00Z'), - } as any, + } as never, { id: 'protest-2', protestingDriverId: 'driver-3', accusedDriverId: 'driver-1', incident: { - lap: { toNumber: () => 7 } as any, - description: { toString: () => 'Incident 2' } as any + lap: { toNumber: () => 7 } as never, + description: { toString: () => 'Incident 2' } as never }, - status: { toString: () => 'reviewed' } as any, + status: { toString: () => 'reviewed' } as never, filedAt: new Date('2024-01-01T10:10:00Z'), - } as any, + } as never, ], drivers: [ - { id: 'driver-1', name: { toString: () => 'Driver One' } } as any, - { id: 'driver-2', name: { toString: () => 'Driver Two' } } as any, - { id: 'driver-3', name: { toString: () => 'Driver Three' } } as any, + { id: 'driver-1', name: { toString: () => 'Driver One' } } as never, + { id: 'driver-2', name: { toString: () => 'Driver Two' } } as never, + { id: 'driver-3', name: { toString: () => 'Driver Three' } } as never, ], }; @@ -125,16 +125,16 @@ describe('RaceProtestsPresenter', () => { protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', incident: { - lap: { toNumber: () => 5 } as any, - description: { toString: () => 'Test' } as any + lap: { toNumber: () => 5 } as never, + description: { toString: () => 'Test' } as never }, - status: { toString: () => 'approved' } as any, + status: { toString: () => 'approved' } as never, filedAt: new Date('2024-01-01T10:00:00Z'), - } as any, + } as never, ], drivers: [ - { id: 'driver-1', name: { toString: () => 'Driver One' } } as any, - { id: 'driver-2', name: { toString: () => 'Driver Two' } } as any, + { id: 'driver-1', name: { toString: () => 'Driver One' } } as never, + { id: 'driver-2', name: { toString: () => 'Driver Two' } } as never, ], }; @@ -152,13 +152,13 @@ describe('RaceProtestsPresenter', () => { protestingDriverId: 'd1', accusedDriverId: 'd2', incident: { - lap: { toNumber: () => 1 } as any, - description: { toString: () => 'test' } as any + lap: { toNumber: () => 1 } as never, + description: { toString: () => 'test' } as never }, - status: { toString: () => 'pending' } as any, + status: { toString: () => 'pending' } as never, filedAt: new Date() - } as any], - drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any], + } as never], + drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never, { id: 'd2', name: { toString: () => 'D2' } } as never], }); expect(presenter.responseModel).toBeDefined(); @@ -181,13 +181,13 @@ describe('RaceProtestsPresenter', () => { protestingDriverId: 'd1', accusedDriverId: 'd2', incident: { - lap: { toNumber: () => 1 } as any, - description: { toString: () => 'test' } as any + lap: { toNumber: () => 1 } as never, + description: { toString: () => 'test' } as never }, - status: { toString: () => 'pending' } as any, + status: { toString: () => 'pending' } as never, filedAt: new Date() - } as any], - drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as any, { id: 'd2', name: { toString: () => 'D2' } } as any], + } as never], + drivers: [{ id: 'd1', name: { toString: () => 'D1' } } as never, { id: 'd2', name: { toString: () => 'D2' } } as never], }; presenter.present(result); diff --git a/apps/api/src/domain/sponsor/Sponsor.http.test.ts b/apps/api/src/domain/sponsor/Sponsor.http.test.ts index 6c5e7d80b..a61823bd3 100644 --- a/apps/api/src/domain/sponsor/Sponsor.http.test.ts +++ b/apps/api/src/domain/sponsor/Sponsor.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Sponsor domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Sponsor domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/sponsor/SponsorController.test.ts b/apps/api/src/domain/sponsor/SponsorController.test.ts index 3f76eb251..ecddd95cc 100644 --- a/apps/api/src/domain/sponsor/SponsorController.test.ts +++ b/apps/api/src/domain/sponsor/SponsorController.test.ts @@ -53,7 +53,7 @@ describe('SponsorController', () => { describe('getEntitySponsorshipPricing', () => { it('should return sponsorship pricing', async () => { const mockResult = { entityType: 'season', entityId: 'season-1', pricing: [] }; - sponsorService.getEntitySponsorshipPricing.mockResolvedValue(mockResult as any); + sponsorService.getEntitySponsorshipPricing.mockResolvedValue(mockResult as never); const result = await controller.getEntitySponsorshipPricing(); @@ -65,7 +65,7 @@ describe('SponsorController', () => { describe('getSponsors', () => { it('should return sponsors list', async () => { const mockResult = { sponsors: [] }; - sponsorService.getSponsors.mockResolvedValue(mockResult as any); + sponsorService.getSponsors.mockResolvedValue(mockResult as never); const result = await controller.getSponsors(); @@ -78,9 +78,9 @@ describe('SponsorController', () => { it('should create sponsor', async () => { const input = { name: 'Test Sponsor', contactEmail: 'test@example.com' }; const mockResult = { sponsor: { id: 's1', name: 'Test Sponsor' } }; - sponsorService.createSponsor.mockResolvedValue(mockResult as any); + sponsorService.createSponsor.mockResolvedValue(mockResult as never); - const result = await controller.createSponsor(input as any); + const result = await controller.createSponsor(input as never); expect(result).toEqual(mockResult); expect(sponsorService.createSponsor).toHaveBeenCalledWith(input); @@ -90,8 +90,8 @@ describe('SponsorController', () => { describe('getSponsorDashboard', () => { it('should return sponsor dashboard', async () => { const sponsorId = 's1'; - const mockResult = { sponsorId, metrics: {} as any, sponsoredLeagues: [], investment: {} as any }; - sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as any); + const mockResult = { sponsorId, metrics: {} as never, sponsoredLeagues: [], investment: {} as never }; + sponsorService.getSponsorDashboard.mockResolvedValue(mockResult as never); const result = await controller.getSponsorDashboard(sponsorId); @@ -122,7 +122,7 @@ describe('SponsorController', () => { currency: 'USD', }, }; - sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as any); + sponsorService.getSponsorSponsorships.mockResolvedValue(mockResult as never); const result = await controller.getSponsorSponsorships(sponsorId); @@ -142,7 +142,7 @@ describe('SponsorController', () => { it('should return sponsor', async () => { const sponsorId = 's1'; const mockResult = { sponsor: { id: sponsorId, name: 'S1' } }; - sponsorService.getSponsor.mockResolvedValue(mockResult as any); + sponsorService.getSponsor.mockResolvedValue(mockResult as never); const result = await controller.getSponsor(sponsorId); @@ -167,7 +167,7 @@ describe('SponsorController', () => { requests: [], totalCount: 0, }; - sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as any); + sponsorService.getPendingSponsorshipRequests.mockResolvedValue(mockResult as never); const result = await controller.getPendingSponsorshipRequests(query); @@ -188,9 +188,9 @@ describe('SponsorController', () => { platformFee: 10, netAmount: 90, }; - sponsorService.acceptSponsorshipRequest.mockResolvedValue(mockResult as any); + sponsorService.acceptSponsorshipRequest.mockResolvedValue(mockResult as never); - const result = await controller.acceptSponsorshipRequest(requestId, input as any); + const result = await controller.acceptSponsorshipRequest(requestId, input as never); expect(result).toEqual(mockResult); expect(sponsorService.acceptSponsorshipRequest).toHaveBeenCalledWith( @@ -204,7 +204,7 @@ describe('SponsorController', () => { const input = { respondedBy: 'u1' }; sponsorService.acceptSponsorshipRequest.mockRejectedValue(new Error('Accept sponsorship request failed')); - await expect(controller.acceptSponsorshipRequest(requestId, input as any)).rejects.toBeInstanceOf(Error); + await expect(controller.acceptSponsorshipRequest(requestId, input as never)).rejects.toBeInstanceOf(Error); }); }); @@ -218,9 +218,9 @@ describe('SponsorController', () => { rejectedAt: new Date(), reason: 'Not interested', }; - sponsorService.rejectSponsorshipRequest.mockResolvedValue(mockResult as any); + sponsorService.rejectSponsorshipRequest.mockResolvedValue(mockResult as never); - const result = await controller.rejectSponsorshipRequest(requestId, input as any); + const result = await controller.rejectSponsorshipRequest(requestId, input as never); expect(result).toEqual(mockResult); expect(sponsorService.rejectSponsorshipRequest).toHaveBeenCalledWith( @@ -235,7 +235,7 @@ describe('SponsorController', () => { const input = { respondedBy: 'u1' }; sponsorService.rejectSponsorshipRequest.mockRejectedValue(new Error('Reject sponsorship request failed')); - await expect(controller.rejectSponsorshipRequest(requestId, input as any)).rejects.toBeInstanceOf(Error); + await expect(controller.rejectSponsorshipRequest(requestId, input as never)).rejects.toBeInstanceOf(Error); }); }); @@ -254,7 +254,7 @@ describe('SponsorController', () => { averageMonthlySpend: 0, }, }; - sponsorService.getSponsorBilling.mockResolvedValue(mockResult as any); + sponsorService.getSponsorBilling.mockResolvedValue(mockResult as never); const result = await controller.getSponsorBilling(sponsorId); @@ -265,8 +265,8 @@ describe('SponsorController', () => { describe('getAvailableLeagues', () => { it('should return available leagues', async () => { - const mockResult: any[] = []; - sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as any); + const mockResult: unknown[] = []; + sponsorService.getAvailableLeagues.mockResolvedValue({ viewModel: mockResult } as never); const result = await controller.getAvailableLeagues(); @@ -279,11 +279,11 @@ describe('SponsorController', () => { it('should return league detail', async () => { const leagueId = 'league-1'; const mockResult = { - league: { id: leagueId } as any, + league: { id: leagueId } as never, drivers: [], races: [], }; - sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as any); + sponsorService.getLeagueDetail.mockResolvedValue({ viewModel: mockResult } as never); const result = await controller.getLeagueDetail(leagueId); @@ -296,11 +296,11 @@ describe('SponsorController', () => { it('should return sponsor settings', async () => { const sponsorId = 's1'; const mockResult = { - profile: {} as any, - notifications: {} as any, - privacy: {} as any, + profile: {} as never, + notifications: {} as never, + privacy: {} as never, }; - sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any); + sponsorService.getSponsorSettings.mockResolvedValue({ viewModel: mockResult } as never); const result = await controller.getSponsorSettings(sponsorId); @@ -314,7 +314,7 @@ describe('SponsorController', () => { const sponsorId = 's1'; const input = {}; const mockResult = { success: true }; - sponsorService.updateSponsorSettings.mockResolvedValue({ viewModel: mockResult } as any); + sponsorService.updateSponsorSettings.mockResolvedValue({ viewModel: mockResult } as never); const result = await controller.updateSponsorSettings(sponsorId, input); diff --git a/apps/api/src/domain/sponsor/SponsorService.test.ts b/apps/api/src/domain/sponsor/SponsorService.test.ts index 099e72489..9201d25f4 100644 --- a/apps/api/src/domain/sponsor/SponsorService.test.ts +++ b/apps/api/src/domain/sponsor/SponsorService.test.ts @@ -167,14 +167,14 @@ describe('SponsorService', () => { it('throws using error.message when details.message is missing', async () => { const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' }; - createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR', message: 'Boom' } as any)); + createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR', message: 'Boom' } as never)); await expect(service.createSponsor(input)).rejects.toThrow('Boom'); }); it('throws default message when details.message and message are missing', async () => { const input: CreateSponsorInputDTO = { name: 'Test', contactEmail: 'test@example.com' }; - createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR' } as any)); + createSponsorUseCase.execute.mockResolvedValue(Result.err({ code: 'VALIDATION_ERROR' } as never)); await expect(service.createSponsor(input)).rejects.toThrow('Failed to create sponsor'); }); @@ -476,7 +476,7 @@ describe('SponsorService', () => { }); it('throws on error', async () => { - getSponsorBillingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' } as any)); + getSponsorBillingUseCase.execute.mockResolvedValue(Result.err({ code: 'REPOSITORY_ERROR' } as never)); await expect(service.getSponsorBilling('s1')).rejects.toThrow('Sponsor billing not found'); }); }); diff --git a/apps/api/src/domain/team/Team.http.test.ts b/apps/api/src/domain/team/Team.http.test.ts index a355ad336..e24353221 100644 --- a/apps/api/src/domain/team/Team.http.test.ts +++ b/apps/api/src/domain/team/Team.http.test.ts @@ -15,7 +15,7 @@ import { FeatureAvailabilityGuard } from '../policy/FeatureAvailabilityGuard'; describe('Team domain (HTTP, module-wiring)', () => { const originalEnv = { ...process.env }; - let app: any; + let app: import("@nestjs/common").INestApplication; beforeAll(async () => { vi.resetModules(); @@ -62,9 +62,9 @@ describe('Team domain (HTTP, module-wiring)', () => { }; app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/team/TeamController.test.ts b/apps/api/src/domain/team/TeamController.test.ts index fa979d782..28cf12c6e 100644 --- a/apps/api/src/domain/team/TeamController.test.ts +++ b/apps/api/src/domain/team/TeamController.test.ts @@ -61,7 +61,7 @@ describe('TeamController', () => { const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false }; service.getDetails.mockResolvedValue(result); - const mockReq = { user: { userId } } as any; + const mockReq = { user: { userId } } as never; const response = await controller.getDetails(teamId, mockReq); @@ -103,7 +103,7 @@ describe('TeamController', () => { const result = { id: 'team-456', success: true }; service.create.mockResolvedValue(result); - const mockReq = { user: { userId } } as any; + const mockReq = { user: { userId } } as never; const response = await controller.create(input, mockReq); @@ -120,7 +120,7 @@ describe('TeamController', () => { const result = { success: true }; service.update.mockResolvedValue(result); - const mockReq = { user: { userId } } as any; + const mockReq = { user: { userId } } as never; const response = await controller.update(teamId, input, mockReq); @@ -157,7 +157,7 @@ describe('TeamController', () => { }); describe('auth guards (HTTP)', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; const sessionPort: { getCurrentSession: () => Promise } = { getCurrentSession: vi.fn(async () => null), @@ -196,9 +196,9 @@ describe('TeamController', () => { const reflector = new Reflector(); app.useGlobalGuards( - new AuthenticationGuard(sessionPort as any), - new AuthorizationGuard(reflector, authorizationService as any), - new FeatureAvailabilityGuard(reflector, policyService as any), + new AuthenticationGuard(sessionPort as never), + new AuthorizationGuard(reflector, authorizationService as never), + new FeatureAvailabilityGuard(reflector, policyService as never), ); await app.init(); diff --git a/apps/api/src/domain/team/TeamProviders.ts b/apps/api/src/domain/team/TeamProviders.ts index 9fa0b7b47..52a1494b3 100644 --- a/apps/api/src/domain/team/TeamProviders.ts +++ b/apps/api/src/domain/team/TeamProviders.ts @@ -1,6 +1,24 @@ import { Provider } from '@nestjs/common'; -import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN, MEDIA_REPOSITORY_TOKEN, MEDIA_RESOLVER_TOKEN } from './TeamTokens'; +import { + IMAGE_SERVICE_TOKEN, + LOGGER_TOKEN, + MEDIA_REPOSITORY_TOKEN, + MEDIA_RESOLVER_TOKEN, + TEAM_REPOSITORY_TOKEN, + TEAM_MEMBERSHIP_REPOSITORY_TOKEN, + DRIVER_REPOSITORY_TOKEN, + TEAM_STATS_REPOSITORY_TOKEN, + GET_ALL_TEAMS_USE_CASE_TOKEN, + GET_TEAM_DETAILS_USE_CASE_TOKEN, + GET_TEAM_MEMBERS_USE_CASE_TOKEN, + GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN, + CREATE_TEAM_USE_CASE_TOKEN, + UPDATE_TEAM_USE_CASE_TOKEN, + GET_DRIVER_TEAM_USE_CASE_TOKEN, + GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN, + JOIN_TEAM_USE_CASE_TOKEN, +} from './TeamTokens'; export { TEAM_REPOSITORY_TOKEN, @@ -15,6 +33,10 @@ export { // Import core interfaces import type { Logger } from '@core/shared/application/Logger'; import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort'; +import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository'; +import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository'; +import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; +import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository'; // Import concrete implementations import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter'; @@ -22,6 +44,17 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger'; import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository'; import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter'; +// Import use cases +import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; +import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase'; +import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase'; +import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase'; +import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase'; +import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase'; +import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase'; +import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase'; +import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase'; + // Import presenters import { AllTeamsPresenter } from './presenters/AllTeamsPresenter'; @@ -69,4 +102,59 @@ export const TeamProviders: Provider[] = [ }, inject: [MEDIA_RESOLVER_TOKEN], }, -]; \ No newline at end of file + // 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], + }, +]; diff --git a/apps/api/src/domain/team/TeamService.test.ts b/apps/api/src/domain/team/TeamService.test.ts index 61448080a..04ef21e01 100644 --- a/apps/api/src/domain/team/TeamService.test.ts +++ b/apps/api/src/domain/team/TeamService.test.ts @@ -23,7 +23,7 @@ type TeamEntityStub = { ownerId: ValueObjectStub; leagues: ValueObjectStub[]; createdAt: { toDate(): Date }; - logoRef: any; + logoRef: unknown; category: string | undefined; isRecruiting: boolean; update: Mock; @@ -124,13 +124,17 @@ describe('TeamService', () => { clear: vi.fn(), }; - service = new TeamService( - teamRepository as unknown as never, - membershipRepository as unknown as never, - driverRepository as unknown as never, - logger, - teamStatsRepository as unknown as never + new GetAllTeamsUseCase(teamRepository as never, membershipRepository as never, teamStatsRepository as never, logger), + new GetTeamDetailsUseCase(teamRepository as never, membershipRepository as never), + new GetTeamMembersUseCase(membershipRepository as never, driverRepository as never, teamRepository as never, logger), + new GetTeamJoinRequestsUseCase(membershipRepository as never, driverRepository as never, teamRepository as never), + new CreateTeamUseCase(teamRepository as never, membershipRepository as never, logger), + new UpdateTeamUseCase(teamRepository as never, membershipRepository as never), + new GetDriverTeamUseCase(teamRepository as never, membershipRepository as never, logger), + new GetTeamMembershipUseCase(membershipRepository as never, logger), + { execute: vi.fn() } as never, // joinTeamUseCase + logger ); }); diff --git a/apps/api/src/domain/team/TeamService.ts b/apps/api/src/domain/team/TeamService.ts index 9bd8473dc..bb0e8823e 100644 --- a/apps/api/src/domain/team/TeamService.ts +++ b/apps/api/src/domain/team/TeamService.ts @@ -12,9 +12,6 @@ import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO'; // Core imports import type { Logger } from '@core/shared/application/Logger'; -import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository'; -import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository'; -import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository'; // Use cases import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase'; @@ -28,39 +25,44 @@ import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/Get import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase'; // Tokens -import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN } from './TeamTokens'; -import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository'; +import { + LOGGER_TOKEN, + GET_ALL_TEAMS_USE_CASE_TOKEN, + GET_TEAM_DETAILS_USE_CASE_TOKEN, + GET_TEAM_MEMBERS_USE_CASE_TOKEN, + GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN, + CREATE_TEAM_USE_CASE_TOKEN, + UPDATE_TEAM_USE_CASE_TOKEN, + GET_DRIVER_TEAM_USE_CASE_TOKEN, + GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN, + JOIN_TEAM_USE_CASE_TOKEN, +} from './TeamTokens'; @Injectable() export class TeamService { constructor( - @Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository, - @Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository, - @Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository, + @Inject(GET_ALL_TEAMS_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase, + @Inject(GET_TEAM_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase, + @Inject(GET_TEAM_MEMBERS_USE_CASE_TOKEN) private readonly getTeamMembersUseCase: GetTeamMembersUseCase, + @Inject(GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN) private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase, + @Inject(CREATE_TEAM_USE_CASE_TOKEN) private readonly createTeamUseCase: CreateTeamUseCase, + @Inject(UPDATE_TEAM_USE_CASE_TOKEN) private readonly updateTeamUseCase: UpdateTeamUseCase, + @Inject(GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase, + @Inject(GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN) private readonly getTeamMembershipUseCase: GetTeamMembershipUseCase, + @Inject(JOIN_TEAM_USE_CASE_TOKEN) private readonly joinTeamUseCase: JoinTeamUseCase, @Inject(LOGGER_TOKEN) private readonly logger: Logger, - @Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository, ) {} async getAll(): Promise { this.logger.debug('[TeamService] Fetching all teams.'); - const useCase = new GetAllTeamsUseCase( - this.teamRepository, - this.membershipRepository, - this.teamStatsRepository, - this.logger - ); - const result = await useCase.execute({}); + const result = await this.getAllTeamsUseCase.execute({}); if (result.isErr()) { - this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error')); - return { teams: [], totalCount: 0 }; - } - - const value = result.value; - if (!value) { + this.logger.error('Error fetching all teams', new Error(result.unwrapErr().details?.message || 'Unknown error')); return { teams: [], totalCount: 0 }; } + const value = result.unwrap(); return { teams: value.teams.map(t => ({ id: t.team.id, @@ -86,18 +88,13 @@ export class TeamService { async getDetails(teamId: string, userId?: string): Promise { this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`); - const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository); - const result = await useCase.execute({ teamId, driverId: userId || '' }); + const result = await this.getTeamDetailsUseCase.execute({ teamId, driverId: userId || '' }); if (result.isErr()) { - this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); - return null; - } - - const value = result.value; - if (!value) { + this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`); return null; } + const value = result.unwrap(); // Convert to DTO return { team: { @@ -123,21 +120,9 @@ export class TeamService { async getMembers(teamId: string): Promise { this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`); - const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger); - const result = await useCase.execute({ teamId }); + const result = await this.getTeamMembersUseCase.execute({ teamId }); if (result.isErr()) { - this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); - return { - members: [], - totalCount: 0, - ownerCount: 0, - managerCount: 0, - memberCount: 0, - }; - } - - const value = result.value; - if (!value) { + this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`); return { members: [], totalCount: 0, @@ -147,6 +132,7 @@ export class TeamService { }; } + const value = result.unwrap(); return { members: value.members.map(m => ({ driverId: m.driver?.id || '', @@ -166,19 +152,9 @@ export class TeamService { async getJoinRequests(teamId: string): Promise { this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`); - const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository); - const result = await useCase.execute({ teamId }); + const result = await this.getTeamJoinRequestsUseCase.execute({ teamId }); if (result.isErr()) { - this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error')); - return { - requests: [], - pendingCount: 0, - totalCount: 0, - }; - } - - const value = result.value; - if (!value) { + this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.unwrapErr().details?.message || 'Unknown error')); return { requests: [], pendingCount: 0, @@ -186,6 +162,7 @@ export class TeamService { }; } + const value = result.unwrap(); return { requests: value.joinRequests.map(r => ({ requestId: r.id, @@ -212,18 +189,13 @@ export class TeamService { leagues: [], }; - const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger); - const result = await useCase.execute(command); + const result = await this.createTeamUseCase.execute(command); if (result.isErr()) { - this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`); - return { id: '', success: false }; - } - - const value = result.value; - if (!value) { + this.logger.error(`Error creating team: ${result.unwrapErr().details?.message || 'Unknown error'}`); return { id: '', success: false }; } + const value = result.unwrap(); return { id: value.team.id, success: true }; } @@ -240,10 +212,9 @@ export class TeamService { updatedBy: userId || '', }; - const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository); - const result = await useCase.execute(command); + const result = await this.updateTeamUseCase.execute(command); if (result.isErr()) { - this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`); + this.logger.error(`Error updating team ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`); return { success: false }; } @@ -253,14 +224,13 @@ export class TeamService { async getDriverTeam(driverId: string): Promise { this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`); - const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger); - const result = await useCase.execute({ driverId }); + const result = await this.getDriverTeamUseCase.execute({ driverId }); if (result.isErr()) { - this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`); + this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`); return null; } - const value = result.value; + const value = result.unwrap(); if (!value || !value.team) { return null; } @@ -290,18 +260,13 @@ export class TeamService { async getMembership(teamId: string, driverId: string): Promise { this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`); - const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger); - const result = await useCase.execute({ teamId, driverId }); + const result = await this.getTeamMembershipUseCase.execute({ teamId, driverId }); if (result.isErr()) { - this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`); - return null; - } - - const value = result.value; - if (!value) { + this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`); return null; } + const value = result.unwrap(); return value.membership ? { role: value.membership.role, joinedAt: value.membership.joinedAt, @@ -316,10 +281,9 @@ export class TeamService { throw new Error('User ID is required'); } - const useCase = new JoinTeamUseCase(this.teamRepository, this.membershipRepository, this.logger); - const result = await useCase.execute({ teamId, driverId: userId }); + const result = await this.joinTeamUseCase.execute({ teamId, driverId: userId }); if (result.isErr()) { - const error = result.error; + const error = result.unwrapErr(); this.logger.error(`Error joining team ${teamId}: ${error?.details?.message || 'Unknown error'}`); return { success: false, error: error?.details?.message || 'Unknown error' }; } diff --git a/apps/api/src/domain/team/TeamTokens.ts b/apps/api/src/domain/team/TeamTokens.ts index 0de8cf099..6356a1154 100644 --- a/apps/api/src/domain/team/TeamTokens.ts +++ b/apps/api/src/domain/team/TeamTokens.ts @@ -6,4 +6,14 @@ export const LOGGER_TOKEN = 'Logger'; export const TEAM_STATS_REPOSITORY_TOKEN = 'ITeamStatsRepository'; export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository'; export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort'; -export const RESULT_REPOSITORY_TOKEN = 'IResultRepository'; \ No newline at end of file +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'); \ No newline at end of file diff --git a/apps/api/src/domain/team/presenters/CreateTeamPresenter.test.ts b/apps/api/src/domain/team/presenters/CreateTeamPresenter.test.ts index b72b84020..5f42b32f6 100644 --- a/apps/api/src/domain/team/presenters/CreateTeamPresenter.test.ts +++ b/apps/api/src/domain/team/presenters/CreateTeamPresenter.test.ts @@ -12,7 +12,7 @@ describe('CreateTeamPresenter', () => { const result = { team: { id: 'team-123', - } as any, + } as never, }; presenter.present(result); @@ -27,7 +27,7 @@ describe('CreateTeamPresenter', () => { const result = { team: { id: 'team-456', - } as any, + } as never, }; presenter.present(result); @@ -41,7 +41,7 @@ describe('CreateTeamPresenter', () => { describe('reset', () => { it('should clear the model', () => { - presenter.present({ team: { id: 'team-123' } } as any); + presenter.present({ team: { id: 'team-123' } } as never); expect(presenter.responseModel).toBeDefined(); presenter.reset(); @@ -55,7 +55,7 @@ describe('CreateTeamPresenter', () => { }); it('should return model after present()', () => { - presenter.present({ team: { id: 'team-123' } } as any); + presenter.present({ team: { id: 'team-123' } } as never); expect(presenter.getResponseModel()).toEqual({ id: 'team-123', success: true, @@ -69,7 +69,7 @@ describe('CreateTeamPresenter', () => { }); it('should return model after present()', () => { - presenter.present({ team: { id: 'team-123' } } as any); + presenter.present({ team: { id: 'team-123' } } as never); expect(presenter.responseModel).toEqual({ id: 'team-123', success: true, diff --git a/apps/api/src/domain/team/presenters/DriverTeamPresenter.test.ts b/apps/api/src/domain/team/presenters/DriverTeamPresenter.test.ts index 88ef04cbc..cef1f8730 100644 --- a/apps/api/src/domain/team/presenters/DriverTeamPresenter.test.ts +++ b/apps/api/src/domain/team/presenters/DriverTeamPresenter.test.ts @@ -13,19 +13,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-123', team: { id: 'team-456', - name: { toString: () => 'Team Alpha' } as any, - tag: { toString: () => 'TA' } as any, - description: { toString: () => 'Best team' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [{ toString: () => 'league-1' } as any], + name: { toString: () => 'Team Alpha' } as never, + tag: { toString: () => 'TA' } as never, + description: { toString: () => 'Best team' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [{ toString: () => 'league-1' } as never], isRecruiting: true, - createdAt: { toDate: () => new Date('2024-01-01') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-01') } as never, + } as never, membership: { role: 'owner' as const, joinedAt: new Date('2024-01-01'), status: 'active' as const, - } as any, + } as never, }; presenter.present(result); @@ -57,19 +57,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-456', team: { id: 'team-789', - name: { toString: () => 'Team Beta' } as any, - tag: { toString: () => 'TB' } as any, - description: { toString: () => '' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [] as any[], + name: { toString: () => 'Team Beta' } as never, + tag: { toString: () => 'TB' } as never, + description: { toString: () => '' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [] as never[], isRecruiting: false, - createdAt: { toDate: () => new Date('2024-01-02') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-02') } as never, + } as never, membership: { role: 'manager' as const, joinedAt: new Date('2024-01-02'), status: 'active' as const, - } as any, + } as never, }; presenter.present(result); @@ -85,19 +85,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-789', team: { id: 'team-abc', - name: { toString: () => 'Team Gamma' } as any, - tag: { toString: () => 'TG' } as any, - description: { toString: () => 'Test team' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [{ toString: () => 'league-2' } as any], + name: { toString: () => 'Team Gamma' } as never, + tag: { toString: () => 'TG' } as never, + description: { toString: () => 'Test team' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [{ toString: () => 'league-2' } as never], isRecruiting: false, - createdAt: { toDate: () => new Date('2024-01-03') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-03') } as never, + } as never, membership: { role: 'driver' as const, joinedAt: new Date('2024-01-03'), status: 'active' as const, - } as any, + } as never, }; presenter.present(result); @@ -113,19 +113,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-123', team: { id: 'team-456', - name: { toString: () => 'Team' } as any, - tag: { toString: () => 'T' } as any, - description: { toString: () => '' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [] as any[], + name: { toString: () => 'Team' } as never, + tag: { toString: () => 'T' } as never, + description: { toString: () => '' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [] as never[], isRecruiting: false, - createdAt: { toDate: () => new Date('2024-01-01') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-01') } as never, + } as never, membership: { role: 'owner' as const, joinedAt: new Date('2024-01-01'), status: 'active' as const, - } as any, + } as never, }; presenter.present(result); @@ -139,19 +139,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-123', team: { id: 'team-456', - name: { toString: () => 'Team' } as any, - tag: { toString: () => 'T' } as any, - description: { toString: () => 'Test' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [] as any[], + name: { toString: () => 'Team' } as never, + tag: { toString: () => 'T' } as never, + description: { toString: () => 'Test' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [] as never[], isRecruiting: false, - createdAt: { toDate: () => new Date('2024-01-01') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-01') } as never, + } as never, membership: { role: 'owner' as const, joinedAt: new Date('2024-01-01'), status: 'active' as const, - } as any, + } as never, }; presenter.present(result); @@ -167,19 +167,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-123', team: { id: 'team-456', - name: { toString: () => 'Team' } as any, - tag: { toString: () => 'T' } as any, - description: { toString: () => '' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [] as any[], + name: { toString: () => 'Team' } as never, + tag: { toString: () => 'T' } as never, + description: { toString: () => '' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [] as never[], isRecruiting: false, - createdAt: { toDate: () => new Date('2024-01-01') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-01') } as never, + } as never, membership: { role: 'owner' as const, joinedAt: new Date('2024-01-01'), status: 'active' as const, - } as any, + } as never, }); expect(presenter.getResponseModel()).toBeDefined(); @@ -198,19 +198,19 @@ describe('DriverTeamPresenter', () => { driverId: 'driver-123', team: { id: 'team-456', - name: { toString: () => 'Team' } as any, - tag: { toString: () => 'T' } as any, - description: { toString: () => '' } as any, - ownerId: { toString: () => 'driver-123' } as any, - leagues: [] as any[], + name: { toString: () => 'Team' } as never, + tag: { toString: () => 'T' } as never, + description: { toString: () => '' } as never, + ownerId: { toString: () => 'driver-123' } as never, + leagues: [] as never[], isRecruiting: false, - createdAt: { toDate: () => new Date('2024-01-01') } as any, - } as any, + createdAt: { toDate: () => new Date('2024-01-01') } as never, + } as never, membership: { role: 'owner' as const, joinedAt: new Date('2024-01-01'), status: 'active' as const, - } as any, + } as never, }); expect(presenter.getResponseModel()).not.toBeNull(); }); diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index 1b7978701..d33a15d71 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -75,7 +75,7 @@ export function getForceReseed(): boolean { /** * When set, the API will generate `openapi.json` and optionally reduce logging noise. * - * Matches previous behavior: any value (even "0") counts as enabled if the var is present. + * Matches previous behavior: unknown value (even "0") counts as enabled if the var is present. */ export function getGenerateOpenapi(): boolean { return isSet(process.env.GENERATE_OPENAPI); diff --git a/apps/api/src/features/features.http.test.ts b/apps/api/src/features/features.http.test.ts index 0dbc0e37f..40e24ee26 100644 --- a/apps/api/src/features/features.http.test.ts +++ b/apps/api/src/features/features.http.test.ts @@ -13,7 +13,7 @@ vi.mock('../config/feature-loader', () => ({ import { loadFeatureConfig } from '../config/feature-loader'; describe('Features HTTP Endpoint', () => { - let app: any; + let app: import("@nestjs/common").INestApplication; beforeEach(async () => { const module = await Test.createTestingModule({ diff --git a/core/media/application/use-cases/GetUploadedMediaUseCase.ts b/core/media/application/use-cases/GetUploadedMediaUseCase.ts new file mode 100644 index 000000000..3e984b6c8 --- /dev/null +++ b/core/media/application/use-cases/GetUploadedMediaUseCase.ts @@ -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> { + 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))); + } + } +} diff --git a/core/media/application/use-cases/ResolveMediaReferenceUseCase.ts b/core/media/application/use-cases/ResolveMediaReferenceUseCase.ts new file mode 100644 index 000000000..0d48543fa --- /dev/null +++ b/core/media/application/use-cases/ResolveMediaReferenceUseCase.ts @@ -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> { + 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))); + } + } +} diff --git a/core/notifications/application/use-cases/GetAllNotificationsUseCase.ts b/core/notifications/application/use-cases/GetAllNotificationsUseCase.ts new file mode 100644 index 000000000..035e57d4f --- /dev/null +++ b/core/notifications/application/use-cases/GetAllNotificationsUseCase.ts @@ -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>> { + 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>({ + 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>({ + code: 'REPOSITORY_ERROR', + details: { message: err.message }, + }); + } + } +} diff --git a/core/racing/application/use-cases/GetDriverUseCase.ts b/core/racing/application/use-cases/GetDriverUseCase.ts new file mode 100644 index 000000000..1b02adaad --- /dev/null +++ b/core/racing/application/use-cases/GetDriverUseCase.ts @@ -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> { + 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))); + } + } +}