website refactor
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -19,8 +19,8 @@ describe('AdminService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
service = new AdminService(
|
||||
mockListUsersUseCase as any,
|
||||
mockGetDashboardStatsUseCase as any
|
||||
mockListUsersUseCase as never,
|
||||
mockGetDashboardStatsUseCase as never
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('AnalyticsController', () => {
|
||||
recordEngagement: vi.fn(),
|
||||
getDashboardData: vi.fn(),
|
||||
getAnalyticsMetrics: vi.fn(),
|
||||
} as any;
|
||||
} as never;
|
||||
controller = new AnalyticsController(service);
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('AnalyticsController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -168,9 +168,9 @@ describe('AnalyticsController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -8,7 +8,7 @@ async function withRequestContext<T>(req: Record<string, unknown>, fn: () => Pro
|
||||
const res = {};
|
||||
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
requestContextMiddleware(req as any, res as any, () => {
|
||||
requestContextMiddleware(req as never, res as never, () => {
|
||||
fn().then(resolve, reject);
|
||||
});
|
||||
});
|
||||
@@ -16,12 +16,12 @@ async function withRequestContext<T>(req: Record<string, unknown>, fn: () => Pro
|
||||
|
||||
describe('ActorFromSession', () => {
|
||||
it('derives actor from authenticated session (request.user), not request payload', async () => {
|
||||
const req: any = {
|
||||
const req: unknown = {
|
||||
user: { userId: 'driver-from-session' },
|
||||
body: { driverId: 'driver-from-body' },
|
||||
};
|
||||
|
||||
await withRequestContext(req, async () => {
|
||||
await withRequestContext(req as Record<string, unknown>, async () => {
|
||||
const actor = getActorFromRequestContext();
|
||||
expect(actor).toEqual({ userId: 'driver-from-session', driverId: 'driver-from-session' });
|
||||
});
|
||||
@@ -32,14 +32,14 @@ describe('ActorFromSession', () => {
|
||||
execute: vi.fn(async () => Result.ok(undefined)),
|
||||
};
|
||||
|
||||
const req: any = {
|
||||
const req: unknown = {
|
||||
user: { userId: 'driver-from-session' },
|
||||
body: { performerDriverId: 'driver-from-body' },
|
||||
};
|
||||
|
||||
await withRequestContext(req, async () => {
|
||||
await withRequestContext(req as Record<string, unknown>, async () => {
|
||||
await expect(
|
||||
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as any),
|
||||
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as never),
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -52,15 +52,15 @@ describe('ActorFromSession', () => {
|
||||
it('permission helper rejects when league admin check fails', async () => {
|
||||
const getLeagueAdminPermissionsUseCase = {
|
||||
execute: vi.fn(async () =>
|
||||
Result.err({ code: 'USER_NOT_MEMBER', details: { message: 'nope' } } as any),
|
||||
Result.err({ code: 'USER_NOT_MEMBER', details: { message: 'nope' } } as never),
|
||||
),
|
||||
};
|
||||
|
||||
const req: any = { user: { userId: 'driver-from-session' } };
|
||||
const req: unknown = { user: { userId: 'driver-from-session' } };
|
||||
|
||||
await withRequestContext(req, async () => {
|
||||
await withRequestContext(req as Record<string, unknown>, async () => {
|
||||
await expect(
|
||||
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as any),
|
||||
requireLeagueAdminOrOwner('league-1', getLeagueAdminPermissionsUseCase as never),
|
||||
).rejects.toThrow('Forbidden');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('AuthController', () => {
|
||||
};
|
||||
(service.getCurrentSession as Mock).mockResolvedValue(session);
|
||||
|
||||
const res = { json: vi.fn() } as any;
|
||||
const res = { json: vi.fn() } as unknown as import('express').Response;
|
||||
|
||||
await controller.getSession(res);
|
||||
|
||||
@@ -129,7 +129,7 @@ describe('AuthController', () => {
|
||||
it('should write JSON null when no session', async () => {
|
||||
(service.getCurrentSession as Mock).mockResolvedValue(null);
|
||||
|
||||
const res = { json: vi.fn() } as any;
|
||||
const res = { json: vi.fn() } as unknown as import('express').Response;
|
||||
|
||||
await controller.getSession(res);
|
||||
|
||||
@@ -151,7 +151,7 @@ describe('AuthController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -201,9 +201,9 @@ describe('AuthController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -11,44 +11,44 @@ function createExecutionContext(request: Record<string, unknown>) {
|
||||
|
||||
describe('AuthenticationGuard', () => {
|
||||
it('attaches request.user.userId from session when missing', async () => {
|
||||
const request: any = {};
|
||||
const request: unknown = {};
|
||||
const sessionPort = {
|
||||
getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })),
|
||||
};
|
||||
|
||||
const guard = new AuthenticationGuard(sessionPort as any);
|
||||
const guard = new AuthenticationGuard(sessionPort as never);
|
||||
|
||||
await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true);
|
||||
await expect(guard.canActivate(createExecutionContext(request as Record<string, unknown>) as never)).resolves.toBe(true);
|
||||
|
||||
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
|
||||
expect(request.user).toEqual({ userId: 'user-1' });
|
||||
expect((request as { user?: unknown }).user).toEqual({ userId: 'user-1' });
|
||||
});
|
||||
|
||||
it('does not override request.user.userId if already present', async () => {
|
||||
const request: any = { user: { userId: 'already-set' } };
|
||||
const request: unknown = { user: { userId: 'already-set' } };
|
||||
const sessionPort = {
|
||||
getCurrentSession: vi.fn(async () => ({ token: 't', user: { id: 'user-1' } })),
|
||||
};
|
||||
|
||||
const guard = new AuthenticationGuard(sessionPort as any);
|
||||
const guard = new AuthenticationGuard(sessionPort as never);
|
||||
|
||||
await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true);
|
||||
await expect(guard.canActivate(createExecutionContext(request as Record<string, unknown>) as never)).resolves.toBe(true);
|
||||
|
||||
expect(sessionPort.getCurrentSession).not.toHaveBeenCalled();
|
||||
expect(request.user).toEqual({ userId: 'already-set' });
|
||||
expect((request as { user?: unknown }).user).toEqual({ userId: 'already-set' });
|
||||
});
|
||||
|
||||
it('leaves request.user undefined when no session exists', async () => {
|
||||
const request: any = {};
|
||||
const request: unknown = {};
|
||||
const sessionPort = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
};
|
||||
|
||||
const guard = new AuthenticationGuard(sessionPort as any);
|
||||
const guard = new AuthenticationGuard(sessionPort as never);
|
||||
|
||||
await expect(guard.canActivate(createExecutionContext(request) as any)).resolves.toBe(true);
|
||||
await expect(guard.canActivate(createExecutionContext(request as Record<string, unknown>) as never)).resolves.toBe(true);
|
||||
|
||||
expect(sessionPort.getCurrentSession).toHaveBeenCalledTimes(1);
|
||||
expect(request.user).toBeUndefined();
|
||||
expect((request as { user?: unknown }).user).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('DashboardController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -108,9 +108,9 @@ describe('DashboardController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -186,7 +186,7 @@ describe('DriverController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -225,9 +225,9 @@ describe('DriverController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -18,6 +18,7 @@ import { GetProfileOverviewUseCase } from '@core/racing/application/use-cases/Ge
|
||||
import { GetTotalDriversUseCase } from '@core/racing/application/use-cases/GetTotalDriversUseCase';
|
||||
import { IsDriverRegisteredForRaceUseCase } from '@core/racing/application/use-cases/IsDriverRegisteredForRaceUseCase';
|
||||
import { UpdateDriverProfileUseCase, type UpdateDriverProfileInput } from '@core/racing/application/use-cases/UpdateDriverProfileUseCase';
|
||||
import { GetDriverUseCase } from '@core/racing/application/use-cases/GetDriverUseCase';
|
||||
|
||||
// Presenters
|
||||
import { CompleteOnboardingPresenter } from './presenters/CompleteOnboardingPresenter';
|
||||
@@ -29,11 +30,9 @@ import { DriverStatsPresenter } from './presenters/DriverStatsPresenter';
|
||||
import { GetDriverLiveriesPresenter } from './presenters/GetDriverLiveriesPresenter';
|
||||
|
||||
// Tokens
|
||||
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import {
|
||||
COMPLETE_DRIVER_ONBOARDING_USE_CASE_TOKEN,
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
GET_DRIVER_LIVERIES_USE_CASE_TOKEN,
|
||||
GET_DRIVERS_LEADERBOARD_USE_CASE_TOKEN,
|
||||
GET_PROFILE_OVERVIEW_USE_CASE_TOKEN,
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
IS_DRIVER_REGISTERED_FOR_RACE_USE_CASE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
UPDATE_DRIVER_PROFILE_USE_CASE_TOKEN,
|
||||
GET_DRIVER_USE_CASE_TOKEN,
|
||||
} from './DriverTokens';
|
||||
|
||||
@Injectable()
|
||||
@@ -60,8 +60,8 @@ export class DriverService {
|
||||
private readonly updateDriverProfileUseCase: UpdateDriverProfileUseCase,
|
||||
@Inject(GET_PROFILE_OVERVIEW_USE_CASE_TOKEN)
|
||||
private readonly getProfileOverviewUseCase: GetProfileOverviewUseCase,
|
||||
@Inject(DRIVER_REPOSITORY_TOKEN)
|
||||
private readonly driverRepository: IDriverRepository, // TODO must be removed from service
|
||||
@Inject(GET_DRIVER_USE_CASE_TOKEN)
|
||||
private readonly getDriverUseCase: GetDriverUseCase,
|
||||
@Inject(LOGGER_TOKEN)
|
||||
private readonly logger: Logger,
|
||||
// Injected presenters (optional for module test compatibility)
|
||||
@@ -138,8 +138,11 @@ export class DriverService {
|
||||
async getCurrentDriver(userId: string): Promise<GetDriverOutputDTO | null> {
|
||||
this.logger.debug(`[DriverService] Fetching current driver for userId: ${userId}`);
|
||||
|
||||
const driver = await this.driverRepository.findById(userId);
|
||||
await this.driverPresenter!.present(Result.ok(driver));
|
||||
const result = await this.getDriverUseCase.execute({ driverId: userId });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().message);
|
||||
}
|
||||
await this.driverPresenter!.present(Result.ok(result.unwrap()));
|
||||
return this.driverPresenter!.getResponseModel();
|
||||
}
|
||||
|
||||
@@ -157,15 +160,22 @@ export class DriverService {
|
||||
await this.updateDriverProfileUseCase.execute(input);
|
||||
|
||||
// Get the updated driver and present it
|
||||
const driver = await this.driverRepository.findById(driverId);
|
||||
await this.driverPresenter!.present(Result.ok(driver));
|
||||
const result = await this.getDriverUseCase.execute({ driverId });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().message);
|
||||
}
|
||||
await this.driverPresenter!.present(Result.ok(result.unwrap()));
|
||||
return this.driverPresenter!.getResponseModel();
|
||||
}
|
||||
|
||||
async getDriver(driverId: string): Promise<GetDriverOutputDTO | null> {
|
||||
this.logger.debug(`[DriverService] Fetching driver for driverId: ${driverId}`);
|
||||
|
||||
const driver = await this.driverRepository.findById(driverId);
|
||||
const result = await this.getDriverUseCase.execute({ driverId });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().message);
|
||||
}
|
||||
const driver = result.unwrap();
|
||||
if (!driver) {
|
||||
return null;
|
||||
}
|
||||
@@ -193,4 +203,4 @@ export class DriverService {
|
||||
await this.getDriverLiveriesPresenter!.present(result);
|
||||
return this.getDriverLiveriesPresenter!.getResponseModel()!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('LeagueController', () => {
|
||||
|
||||
it('getTotalLeagues should return total leagues', async () => {
|
||||
const mockResult = { totalLeagues: 1 };
|
||||
leagueService.getTotalLeagues.mockResolvedValue(mockResult as any);
|
||||
leagueService.getTotalLeagues.mockResolvedValue(mockResult as never);
|
||||
|
||||
const result = await controller.getTotalLeagues();
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('LeagueController', () => {
|
||||
|
||||
it('getAllLeaguesWithCapacity should return leagues and totalCount', async () => {
|
||||
const mockResult = { leagues: [], totalCount: 0 };
|
||||
leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as any);
|
||||
leagueService.getAllLeaguesWithCapacity.mockResolvedValue(mockResult as never);
|
||||
|
||||
const result = await controller.getAllLeaguesWithCapacity();
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('LeagueController', () => {
|
||||
|
||||
it('getLeagueStandings should return standings', async () => {
|
||||
const mockResult = { standings: [] };
|
||||
leagueService.getLeagueStandings.mockResolvedValue(mockResult as any);
|
||||
leagueService.getLeagueStandings.mockResolvedValue(mockResult as never);
|
||||
|
||||
const result = await controller.getLeagueStandings('league-1');
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('LeagueController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -105,9 +105,9 @@ describe('LeagueController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import { JoinRequest } from '@core/racing/domain/entities/JoinRequest';
|
||||
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
||||
|
||||
describe('League roster join request mutations (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -123,15 +123,16 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
app = module.createNestApplication();
|
||||
|
||||
// Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner().
|
||||
app.use(requestContextMiddleware as any);
|
||||
app.use(requestContextMiddleware as never);
|
||||
|
||||
// Test-only auth injection: emulate an authenticated session by setting request.user.
|
||||
app.use((req: any, _res: any, next: any) => {
|
||||
const userId = req.headers['x-test-user-id'];
|
||||
app.use((req: unknown, _res: unknown, next: unknown) => {
|
||||
const r = req as { headers: Record<string, string>, user?: { userId: string } };
|
||||
const userId = r.headers['x-test-user-id'];
|
||||
if (typeof userId === 'string' && userId.length > 0) {
|
||||
req.user = { userId };
|
||||
r.user = { userId };
|
||||
}
|
||||
next();
|
||||
(next as () => void)();
|
||||
});
|
||||
|
||||
app.useGlobalPipes(
|
||||
@@ -144,8 +145,8 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
@@ -202,7 +203,7 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(joinRequests.body)).toBe(true);
|
||||
expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeUndefined();
|
||||
expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeUndefined();
|
||||
|
||||
const members = await request(app.getHttpServer())
|
||||
.get('/leagues/league-1/admin/roster/members')
|
||||
@@ -210,7 +211,7 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(members.body)).toBe(true);
|
||||
expect(members.body.some((m: any) => m.driverId === 'driver-2')).toBe(true);
|
||||
expect(members.body.some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(true);
|
||||
});
|
||||
|
||||
it('reject removes request only; roster reads reflect changes', async () => {
|
||||
@@ -232,7 +233,7 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(joinRequests.body)).toBe(true);
|
||||
expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeUndefined();
|
||||
expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeUndefined();
|
||||
|
||||
const members = await request(app.getHttpServer())
|
||||
.get('/leagues/league-1/admin/roster/members')
|
||||
@@ -240,7 +241,7 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(members.body)).toBe(true);
|
||||
expect(members.body.some((m: any) => m.driverId === 'driver-2')).toBe(false);
|
||||
expect(members.body.some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(false);
|
||||
});
|
||||
|
||||
it('approve returns error when league is full and keeps request pending', async () => {
|
||||
@@ -264,6 +265,6 @@ describe('League roster join request mutations (HTTP)', () => {
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(joinRequests.body)).toBe(true);
|
||||
expect(joinRequests.body.find((r: any) => r.id === 'jr-1')).toBeDefined();
|
||||
expect(joinRequests.body.find((r: unknown) => (r as { id: string }).id === 'jr-1')).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,7 @@ import { Driver } from '@core/racing/domain/entities/Driver';
|
||||
import { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
|
||||
|
||||
describe('League roster member mutations (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -98,15 +98,16 @@ describe('League roster member mutations (HTTP)', () => {
|
||||
app = module.createNestApplication();
|
||||
|
||||
// Required for getActorFromRequestContext() used by requireLeagueAdminOrOwner().
|
||||
app.use(requestContextMiddleware as any);
|
||||
app.use(requestContextMiddleware as never);
|
||||
|
||||
// Test-only auth injection: emulate an authenticated session by setting request.user.
|
||||
app.use((req: any, _res: any, next: any) => {
|
||||
const userId = req.headers['x-test-user-id'];
|
||||
app.use((req: unknown, _res: unknown, next: unknown) => {
|
||||
const r = req as { headers: Record<string, string>, user?: { userId: string } };
|
||||
const userId = r.headers['x-test-user-id'];
|
||||
if (typeof userId === 'string' && userId.length > 0) {
|
||||
req.user = { userId };
|
||||
r.user = { userId };
|
||||
}
|
||||
next();
|
||||
(next as () => void)();
|
||||
});
|
||||
|
||||
app.useGlobalPipes(
|
||||
@@ -119,8 +120,8 @@ describe('League roster member mutations (HTTP)', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
@@ -173,9 +174,9 @@ describe('League roster member mutations (HTTP)', () => {
|
||||
|
||||
expect(Array.isArray(members.body)).toBe(true);
|
||||
|
||||
const updated = (members.body as any[]).find(m => m.driverId === 'driver-2');
|
||||
const updated = (members.body as { driverId: string, role: string }[]).find((m: unknown) => (m as { driverId: string }).driverId === 'driver-2');
|
||||
expect(updated).toBeDefined();
|
||||
expect(updated.role).toBe('steward');
|
||||
expect(updated?.role).toBe('steward');
|
||||
});
|
||||
|
||||
it('member removal is reflected in roster members read', async () => {
|
||||
@@ -192,6 +193,6 @@ describe('League roster member mutations (HTTP)', () => {
|
||||
.expect(200);
|
||||
|
||||
expect(Array.isArray(members.body)).toBe(true);
|
||||
expect((members.body as any[]).some(m => m.driverId === 'driver-2')).toBe(false);
|
||||
expect((members.body as never[]).some((m: unknown) => (m as { driverId: string }).driverId === 'driver-2')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -8,7 +8,7 @@ async function withUserId<T>(userId: string, fn: () => Promise<T>): Promise<T> {
|
||||
const res = {};
|
||||
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
requestContextMiddleware(req as any, res as any, () => {
|
||||
requestContextMiddleware(req as never, res as never, () => {
|
||||
fn().then(resolve, reject);
|
||||
});
|
||||
});
|
||||
@@ -19,13 +19,13 @@ describe('LeagueService', () => {
|
||||
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
||||
|
||||
const ok = async () => Result.ok(undefined);
|
||||
const err = async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } });
|
||||
const err = async () => Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never);
|
||||
|
||||
const getAllLeaguesWithCapacityUseCase: any = { execute: vi.fn(async () => Result.ok({ leagues: [] })) };
|
||||
const getAllLeaguesWithCapacityAndScoringUseCase: any = { execute: vi.fn(ok) };
|
||||
const getAllLeaguesWithCapacityUseCase = { execute: vi.fn(async () => Result.ok({ leagues: [] })) };
|
||||
const getAllLeaguesWithCapacityAndScoringUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueStandingsUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueStatsUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueFullConfigUseCase: any = { execute: vi.fn(ok) };
|
||||
const getLeagueFullConfigUseCase = { execute: vi.fn(ok) };
|
||||
const getLeagueScoringConfigUseCase = { execute: vi.fn(ok) };
|
||||
const listLeagueScoringPresetsUseCase = { execute: vi.fn(ok) };
|
||||
const joinLeagueUseCase = { execute: vi.fn(ok) };
|
||||
@@ -120,74 +120,74 @@ describe('LeagueService', () => {
|
||||
const publishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: true })) };
|
||||
const unpublishLeagueSeasonSchedulePresenter = { present: vi.fn(), getResponseModel: vi.fn(() => ({ success: true, published: false })) };
|
||||
|
||||
const service = new (LeagueService as any)(
|
||||
getAllLeaguesWithCapacityUseCase as any,
|
||||
getAllLeaguesWithCapacityAndScoringUseCase as any,
|
||||
getLeagueStandingsUseCase as any,
|
||||
getLeagueStatsUseCase as any,
|
||||
getLeagueFullConfigUseCase as any,
|
||||
getLeagueScoringConfigUseCase as any,
|
||||
listLeagueScoringPresetsUseCase as any,
|
||||
joinLeagueUseCase as any,
|
||||
transferLeagueOwnershipUseCase as any,
|
||||
createLeagueWithSeasonAndScoringUseCase as any,
|
||||
getTotalLeaguesUseCase as any,
|
||||
getLeagueJoinRequestsUseCase as any,
|
||||
approveLeagueJoinRequestUseCase as any,
|
||||
rejectLeagueJoinRequestUseCase as any,
|
||||
removeLeagueMemberUseCase as any,
|
||||
updateLeagueMemberRoleUseCase as any,
|
||||
getLeagueOwnerSummaryUseCase as any,
|
||||
getLeagueProtestsUseCase as any,
|
||||
getLeagueSeasonsUseCase as any,
|
||||
getLeagueMembershipsUseCase as any,
|
||||
getLeagueScheduleUseCase as any,
|
||||
getLeagueAdminPermissionsUseCase as any,
|
||||
getLeagueWalletUseCase as any,
|
||||
withdrawFromLeagueWalletUseCase as any,
|
||||
getSeasonSponsorshipsUseCase as any,
|
||||
createLeagueSeasonScheduleRaceUseCase as any,
|
||||
updateLeagueSeasonScheduleRaceUseCase as any,
|
||||
deleteLeagueSeasonScheduleRaceUseCase as any,
|
||||
publishLeagueSeasonScheduleUseCase as any,
|
||||
unpublishLeagueSeasonScheduleUseCase as any,
|
||||
logger as any,
|
||||
allLeaguesWithCapacityPresenter as any,
|
||||
allLeaguesWithCapacityAndScoringPresenter as any,
|
||||
leagueStandingsPresenter as any,
|
||||
leagueProtestsPresenter as any,
|
||||
seasonSponsorshipsPresenter as any,
|
||||
leagueScoringPresetsPresenter as any,
|
||||
approveLeagueJoinRequestPresenter as any,
|
||||
createLeaguePresenter as any,
|
||||
getLeagueAdminPermissionsPresenter as any,
|
||||
getLeagueMembershipsPresenter as any,
|
||||
getLeagueOwnerSummaryPresenter as any,
|
||||
getLeagueSeasonsPresenter as any,
|
||||
joinLeaguePresenter as any,
|
||||
leagueSchedulePresenter as any,
|
||||
leagueStatsPresenter as any,
|
||||
rejectLeagueJoinRequestPresenter as any,
|
||||
removeLeagueMemberPresenter as any,
|
||||
totalLeaguesPresenter as any,
|
||||
transferLeagueOwnershipPresenter as any,
|
||||
updateLeagueMemberRolePresenter as any,
|
||||
leagueConfigPresenter as any,
|
||||
leagueScoringConfigPresenter as any,
|
||||
getLeagueWalletPresenter as any,
|
||||
withdrawFromLeagueWalletPresenter as any,
|
||||
leagueJoinRequestsPresenter as any,
|
||||
createLeagueSeasonScheduleRacePresenter as any,
|
||||
updateLeagueSeasonScheduleRacePresenter as any,
|
||||
deleteLeagueSeasonScheduleRacePresenter as any,
|
||||
publishLeagueSeasonSchedulePresenter as any,
|
||||
unpublishLeagueSeasonSchedulePresenter as any,
|
||||
const service = new (LeagueService as unknown as { new (...args: never[]): LeagueService })(
|
||||
getAllLeaguesWithCapacityUseCase as never,
|
||||
getAllLeaguesWithCapacityAndScoringUseCase as never,
|
||||
getLeagueStandingsUseCase as never,
|
||||
getLeagueStatsUseCase as never,
|
||||
getLeagueFullConfigUseCase as never,
|
||||
getLeagueScoringConfigUseCase as never,
|
||||
listLeagueScoringPresetsUseCase as never,
|
||||
joinLeagueUseCase as never,
|
||||
transferLeagueOwnershipUseCase as never,
|
||||
createLeagueWithSeasonAndScoringUseCase as never,
|
||||
getTotalLeaguesUseCase as never,
|
||||
getLeagueJoinRequestsUseCase as never,
|
||||
approveLeagueJoinRequestUseCase as never,
|
||||
rejectLeagueJoinRequestUseCase as never,
|
||||
removeLeagueMemberUseCase as never,
|
||||
updateLeagueMemberRoleUseCase as never,
|
||||
getLeagueOwnerSummaryUseCase as never,
|
||||
getLeagueProtestsUseCase as never,
|
||||
getLeagueSeasonsUseCase as never,
|
||||
getLeagueMembershipsUseCase as never,
|
||||
getLeagueScheduleUseCase as never,
|
||||
getLeagueAdminPermissionsUseCase as never,
|
||||
getLeagueWalletUseCase as never,
|
||||
withdrawFromLeagueWalletUseCase as never,
|
||||
getSeasonSponsorshipsUseCase as never,
|
||||
createLeagueSeasonScheduleRaceUseCase as never,
|
||||
updateLeagueSeasonScheduleRaceUseCase as never,
|
||||
deleteLeagueSeasonScheduleRaceUseCase as never,
|
||||
publishLeagueSeasonScheduleUseCase as never,
|
||||
unpublishLeagueSeasonScheduleUseCase as never,
|
||||
logger as never,
|
||||
allLeaguesWithCapacityPresenter as never,
|
||||
allLeaguesWithCapacityAndScoringPresenter as never,
|
||||
leagueStandingsPresenter as never,
|
||||
leagueProtestsPresenter as never,
|
||||
seasonSponsorshipsPresenter as never,
|
||||
leagueScoringPresetsPresenter as never,
|
||||
approveLeagueJoinRequestPresenter as never,
|
||||
createLeaguePresenter as never,
|
||||
getLeagueAdminPermissionsPresenter as never,
|
||||
getLeagueMembershipsPresenter as never,
|
||||
getLeagueOwnerSummaryPresenter as never,
|
||||
getLeagueSeasonsPresenter as never,
|
||||
joinLeaguePresenter as never,
|
||||
leagueSchedulePresenter as never,
|
||||
leagueStatsPresenter as never,
|
||||
rejectLeagueJoinRequestPresenter as never,
|
||||
removeLeagueMemberPresenter as never,
|
||||
totalLeaguesPresenter as never,
|
||||
transferLeagueOwnershipPresenter as never,
|
||||
updateLeagueMemberRolePresenter as never,
|
||||
leagueConfigPresenter as never,
|
||||
leagueScoringConfigPresenter as never,
|
||||
getLeagueWalletPresenter as never,
|
||||
withdrawFromLeagueWalletPresenter as never,
|
||||
leagueJoinRequestsPresenter as never,
|
||||
createLeagueSeasonScheduleRacePresenter as never,
|
||||
updateLeagueSeasonScheduleRacePresenter as never,
|
||||
deleteLeagueSeasonScheduleRacePresenter as never,
|
||||
publishLeagueSeasonSchedulePresenter as never,
|
||||
unpublishLeagueSeasonSchedulePresenter as never,
|
||||
|
||||
// Roster admin read delegation (added for strict TDD)
|
||||
getLeagueRosterMembersUseCase as any,
|
||||
getLeagueRosterJoinRequestsUseCase as any,
|
||||
getLeagueRosterMembersPresenter as any,
|
||||
getLeagueRosterJoinRequestsPresenter as any,
|
||||
getLeagueRosterMembersUseCase as never,
|
||||
getLeagueRosterJoinRequestsUseCase as never,
|
||||
getLeagueRosterMembersPresenter as never,
|
||||
getLeagueRosterJoinRequestsPresenter as never,
|
||||
);
|
||||
|
||||
await expect(service.getTotalLeagues()).resolves.toEqual({ total: 1 });
|
||||
@@ -196,13 +196,13 @@ describe('LeagueService', () => {
|
||||
});
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.approveLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as any)).resolves.toEqual({
|
||||
await expect(service.approveLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as never)).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.rejectLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as any)).resolves.toEqual({
|
||||
await expect(service.rejectLeagueJoinRequest({ leagueId: 'l1', requestId: 'r1' } as never)).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
@@ -212,11 +212,11 @@ describe('LeagueService', () => {
|
||||
);
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.getLeagueAdminPermissions({ leagueId: 'l1' } as any)).resolves.toEqual({ canManage: true });
|
||||
await expect(service.getLeagueAdminPermissions({ leagueId: 'l1' } as never)).resolves.toEqual({ canManage: true });
|
||||
});
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.removeLeagueMember({ leagueId: 'l1', targetDriverId: 'd1' } as any)).resolves.toEqual({
|
||||
await expect(service.removeLeagueMember({ leagueId: 'l1', targetDriverId: 'd1' } as never)).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
@@ -232,7 +232,7 @@ describe('LeagueService', () => {
|
||||
});
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.updateLeagueMemberRole('l1', 'd1', { newRole: 'member' } as any)).resolves.toEqual({
|
||||
await expect(service.updateLeagueMemberRole('l1', 'd1', { newRole: 'member' } as never)).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
@@ -248,11 +248,11 @@ describe('LeagueService', () => {
|
||||
newRole: 'member',
|
||||
});
|
||||
|
||||
await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as any)).resolves.toEqual({ ownerId: 'o1' });
|
||||
await expect(service.getLeagueProtests({ leagueId: 'l1' } as any)).resolves.toEqual({ protests: [] });
|
||||
await expect(service.getLeagueSeasons({ leagueId: 'l1' } as any)).resolves.toEqual([]);
|
||||
await expect(service.getLeagueOwnerSummary({ leagueId: 'l1' } as never)).resolves.toEqual({ ownerId: 'o1' });
|
||||
await expect(service.getLeagueProtests({ leagueId: 'l1' } as never)).resolves.toEqual({ protests: [] });
|
||||
await expect(service.getLeagueSeasons({ leagueId: 'l1' } as never)).resolves.toEqual([]);
|
||||
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toEqual({ form: {} });
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toEqual({ form: {} });
|
||||
await expect(service.getLeagueScoringConfig('l1')).resolves.toEqual({ config: {} });
|
||||
|
||||
await expect(service.getLeagueMemberships('l1')).resolves.toEqual({ memberships: [] });
|
||||
@@ -272,7 +272,7 @@ describe('LeagueService', () => {
|
||||
|
||||
// Roster admin read endpoints must be admin-gated (auth boundary) and must not execute core use cases on 403.
|
||||
getLeagueAdminPermissionsUseCase.execute.mockResolvedValueOnce(
|
||||
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as any,
|
||||
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as never,
|
||||
);
|
||||
getLeagueRosterMembersUseCase.execute.mockClear();
|
||||
await withUserId('user-2', async () => {
|
||||
@@ -285,7 +285,7 @@ describe('LeagueService', () => {
|
||||
expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
|
||||
getLeagueScheduleUseCase.execute.mockClear();
|
||||
await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as any)).resolves.toEqual({
|
||||
await expect(service.getLeagueSchedule('l1', { seasonId: 'season-x' } as never)).resolves.toEqual({
|
||||
seasonId: 'season-1',
|
||||
published: false,
|
||||
races: [],
|
||||
@@ -293,7 +293,7 @@ describe('LeagueService', () => {
|
||||
expect(getLeagueScheduleUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 'season-x' });
|
||||
await expect(service.getLeagueStats('l1')).resolves.toEqual({ stats: {} });
|
||||
|
||||
await expect(service.createLeague({ name: 'n', description: 'd', ownerId: 'o' } as any)).resolves.toEqual({ id: 'l1' });
|
||||
await expect(service.createLeague({ name: 'n', description: 'd', ownerId: 'o' } as never)).resolves.toEqual({ id: 'l1' });
|
||||
await expect(service.listLeagueScoringPresets()).resolves.toEqual({ presets: [] });
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
@@ -301,7 +301,7 @@ describe('LeagueService', () => {
|
||||
});
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as never)).resolves.toEqual({ success: true });
|
||||
});
|
||||
|
||||
// Transfer ownership must be admin-gated and actor-derived (no payload owner/admin IDs)
|
||||
@@ -310,7 +310,7 @@ describe('LeagueService', () => {
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(
|
||||
service.transferLeagueOwnership('l1', { newOwnerId: 'o2', currentOwnerId: 'spoof' } as any),
|
||||
service.transferLeagueOwnership('l1', { newOwnerId: 'o2', currentOwnerId: 'spoof' } as never),
|
||||
).resolves.toEqual({ success: true });
|
||||
});
|
||||
|
||||
@@ -327,11 +327,11 @@ describe('LeagueService', () => {
|
||||
|
||||
// Unauthorized (non-admin/owner) actors are rejected
|
||||
getLeagueAdminPermissionsUseCase.execute.mockResolvedValueOnce(
|
||||
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as any,
|
||||
Result.err({ code: 'FORBIDDEN', details: { message: 'nope' } }) as never,
|
||||
);
|
||||
transferLeagueOwnershipUseCase.execute.mockClear();
|
||||
await withUserId('user-2', async () => {
|
||||
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as any)).rejects.toThrow('Forbidden');
|
||||
await expect(service.transferLeagueOwnership('l1', { newOwnerId: 'o2' } as never)).rejects.toThrow('Forbidden');
|
||||
});
|
||||
expect(transferLeagueOwnershipUseCase.execute).not.toHaveBeenCalled();
|
||||
|
||||
@@ -349,11 +349,11 @@ describe('LeagueService', () => {
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(
|
||||
service.publishLeagueSeasonSchedule('l1', 'season-1', {} as any),
|
||||
service.publishLeagueSeasonSchedule('l1', 'season-1', {} as never),
|
||||
).resolves.toEqual({ success: true, published: true });
|
||||
|
||||
await expect(
|
||||
service.unpublishLeagueSeasonSchedule('l1', 'season-1', {} as any),
|
||||
service.unpublishLeagueSeasonSchedule('l1', 'season-1', {} as never),
|
||||
).resolves.toEqual({ success: true, published: false });
|
||||
|
||||
await expect(
|
||||
@@ -361,14 +361,14 @@ describe('LeagueService', () => {
|
||||
track: 'Spa',
|
||||
car: 'GT3',
|
||||
scheduledAtIso: new Date('2025-01-10T20:00:00Z').toISOString(),
|
||||
} as any),
|
||||
} as never),
|
||||
).resolves.toEqual({ raceId: 'race-1' });
|
||||
|
||||
await expect(
|
||||
service.updateLeagueSeasonScheduleRace('l1', 'season-1', 'race-1', {
|
||||
track: 'Monza',
|
||||
car: 'LMP2',
|
||||
} as any),
|
||||
} as never),
|
||||
).resolves.toEqual({ success: true });
|
||||
|
||||
await expect(
|
||||
@@ -403,7 +403,7 @@ describe('LeagueService', () => {
|
||||
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(
|
||||
service.withdrawFromLeagueWallet('l1', { amount: 1, currency: 'USD', destinationAccount: 'x' } as any),
|
||||
service.withdrawFromLeagueWallet('l1', { amount: 1, currency: 'USD', destinationAccount: 'x' } as never),
|
||||
).resolves.toEqual({
|
||||
success: true,
|
||||
});
|
||||
@@ -421,14 +421,14 @@ describe('LeagueService', () => {
|
||||
await expect(service.getAllLeaguesWithCapacityAndScoring()).resolves.toEqual({ leagues: [], totalCount: 0 });
|
||||
|
||||
// Error branch: getAllLeaguesWithCapacity throws on result.isErr()
|
||||
getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } }));
|
||||
getAllLeaguesWithCapacityUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never));
|
||||
await expect(service.getAllLeaguesWithCapacity()).rejects.toThrow('REPOSITORY_ERROR');
|
||||
|
||||
// Error branches: try/catch returning null
|
||||
getLeagueFullConfigUseCase.execute.mockImplementationOnce(async () => {
|
||||
throw new Error('boom');
|
||||
});
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toBeNull();
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull();
|
||||
|
||||
getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => {
|
||||
throw new Error('boom');
|
||||
@@ -439,7 +439,7 @@ describe('LeagueService', () => {
|
||||
getLeagueFullConfigUseCase.execute.mockImplementationOnce(async () => {
|
||||
throw 'boom';
|
||||
});
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as any)).resolves.toBeNull();
|
||||
await expect(service.getLeagueFullConfig({ leagueId: 'l1' } as never)).resolves.toBeNull();
|
||||
|
||||
getLeagueScoringConfigUseCase.execute.mockImplementationOnce(async () => {
|
||||
throw 'boom';
|
||||
@@ -447,7 +447,7 @@ describe('LeagueService', () => {
|
||||
await expect(service.getLeagueScoringConfig('l1')).resolves.toBeNull();
|
||||
|
||||
// getLeagueAdmin error branch: fullConfigResult is Err
|
||||
getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } }));
|
||||
getLeagueFullConfigUseCase.execute.mockResolvedValueOnce(Result.err({ code: 'REPOSITORY_ERROR', details: { message: 'boom' } } as never));
|
||||
await withUserId('user-1', async () => {
|
||||
await expect(service.getLeagueAdmin('l1')).rejects.toThrow('REPOSITORY_ERROR');
|
||||
});
|
||||
@@ -467,4 +467,4 @@ describe('LeagueService', () => {
|
||||
// keep lint happy (ensures err() used)
|
||||
await err();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { LeagueConfigPresenter } from './LeagueConfigPresenter';
|
||||
|
||||
describe('LeagueConfigPresenter', () => {
|
||||
const createFullConfig = (overrides: Partial<any> = {}): any => {
|
||||
const base: any = {
|
||||
const createFullConfig = (overrides: Partial<any> = {}): unknown => {
|
||||
const base: unknown = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
league: {
|
||||
id: 'league-1',
|
||||
@@ -12,7 +12,7 @@ describe('LeagueConfigPresenter', () => {
|
||||
ownerId: 'owner-1',
|
||||
settings: { pointsSystem: 'custom' },
|
||||
createdAt: new Date(),
|
||||
} as any,
|
||||
} as never,
|
||||
activeSeason: {
|
||||
id: 'season-1',
|
||||
leagueId: 'league-1',
|
||||
@@ -21,9 +21,9 @@ describe('LeagueConfigPresenter', () => {
|
||||
status: 'planned',
|
||||
schedule: {
|
||||
startDate: new Date('2025-01-05T19:00:00Z'),
|
||||
timeOfDay: { hour: 20, minute: 0 } as any,
|
||||
} as any,
|
||||
dropPolicy: { strategy: 'bestNResults', n: 3 } as any,
|
||||
timeOfDay: { hour: 20, minute: 0 } as never,
|
||||
} as never,
|
||||
dropPolicy: { strategy: 'bestNResults', n: 3 } as never,
|
||||
stewardingConfig: {
|
||||
decisionMode: 'steward_vote',
|
||||
requiredVotes: 3,
|
||||
@@ -34,8 +34,8 @@ describe('LeagueConfigPresenter', () => {
|
||||
stewardingClosesHours: 72,
|
||||
notifyAccusedOnProtest: true,
|
||||
notifyOnVoteRequired: true,
|
||||
} as any,
|
||||
} as any,
|
||||
} as never,
|
||||
} as never,
|
||||
scoringConfig: {
|
||||
id: 'scoring-1',
|
||||
seasonId: 'season-1',
|
||||
@@ -43,17 +43,17 @@ describe('LeagueConfigPresenter', () => {
|
||||
{
|
||||
id: 'champ-1',
|
||||
name: 'Drivers',
|
||||
type: 'driver' as any,
|
||||
sessionTypes: ['race'] as any,
|
||||
type: 'driver' as never,
|
||||
sessionTypes: ['race'] as never,
|
||||
pointsTableBySessionType: {
|
||||
race: {
|
||||
getPointsForPosition: (pos: number) => (pos === 1 ? 25 : 0),
|
||||
} as any,
|
||||
} as never,
|
||||
},
|
||||
dropScorePolicy: { strategy: 'bestNResults', count: 3 } as any,
|
||||
dropScorePolicy: { strategy: 'bestNResults', count: 3 } as never,
|
||||
},
|
||||
],
|
||||
} as any,
|
||||
} as never,
|
||||
game: undefined,
|
||||
...overrides,
|
||||
};
|
||||
@@ -65,7 +65,7 @@ describe('LeagueConfigPresenter', () => {
|
||||
const presenter = new LeagueConfigPresenter();
|
||||
const fullConfig = createFullConfig();
|
||||
|
||||
presenter.present({ config: fullConfig });
|
||||
presenter.present({ config: fullConfig } as never);
|
||||
const vm = presenter.getViewModel()!;
|
||||
|
||||
expect(vm).not.toBeNull();
|
||||
|
||||
@@ -10,8 +10,28 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
|
||||
}
|
||||
|
||||
present(result: GetLeagueFullConfigResult): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const dto = result.config as any;
|
||||
const dto = result.config as unknown as {
|
||||
league: { id: string; name: string; description: string; settings: { pointsSystem: string } };
|
||||
activeSeason?: {
|
||||
stewardingConfig?: {
|
||||
decisionMode: string;
|
||||
requireDefense: boolean;
|
||||
defenseTimeLimit: number;
|
||||
voteTimeLimit: number;
|
||||
protestDeadlineHours: number;
|
||||
stewardingClosesHours: number;
|
||||
notifyAccusedOnProtest: boolean;
|
||||
notifyOnVoteRequired: boolean;
|
||||
requiredVotes?: number;
|
||||
};
|
||||
dropPolicy?: { strategy: string; n?: number };
|
||||
schedule?: {
|
||||
startDate?: Date;
|
||||
timeOfDay?: { hour: number; minute: number };
|
||||
};
|
||||
};
|
||||
scoringConfig?: { championships: { sessionTypes: string[]; pointsTableBySessionType: Record<string, { getPointsForPosition: (pos: number) => number }> }[] };
|
||||
};
|
||||
const league = dto.league;
|
||||
const settings = league.settings;
|
||||
const stewarding = dto.activeSeason?.stewardingConfig;
|
||||
@@ -53,7 +73,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
|
||||
},
|
||||
dropPolicy: {
|
||||
strategy: dropPolicy?.strategy === 'none' ? 'none' : 'worst_n',
|
||||
n: dropPolicy?.n,
|
||||
...(dropPolicy?.n !== undefined ? { n: dropPolicy.n } : {}),
|
||||
},
|
||||
timings: {
|
||||
raceDayOfWeek,
|
||||
@@ -69,7 +89,7 @@ export class LeagueConfigPresenter implements UseCaseOutputPort<GetLeagueFullCon
|
||||
stewardingClosesHours: stewarding?.stewardingClosesHours ?? 168,
|
||||
notifyAccusedOnProtest: stewarding?.notifyAccusedOnProtest ?? true,
|
||||
notifyOnVoteRequired: stewarding?.notifyOnVoteRequired ?? true,
|
||||
requiredVotes: stewarding?.requiredVotes,
|
||||
...(stewarding?.requiredVotes !== undefined ? { requiredVotes: stewarding.requiredVotes } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ describe('LeagueJoinRequestsPresenter', () => {
|
||||
requestedAt: new Date('2023-01-01'),
|
||||
message: 'Please accept me',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
driver: { id: 'driver-1', name: 'John Doe' } as any,
|
||||
driver: { id: 'driver-1', name: 'John Doe' } as never,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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$/);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -35,6 +35,8 @@ describe('MediaController', () => {
|
||||
deleteMedia: ReturnType<typeof vi.fn>;
|
||||
getAvatar: ReturnType<typeof vi.fn>;
|
||||
updateAvatar: ReturnType<typeof vi.fn>;
|
||||
getUploadedMedia: ReturnType<typeof vi.fn>;
|
||||
resolveMediaReference: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
let generationService: MediaGenerationService & {
|
||||
generateDriverAvatar: ReturnType<typeof vi.fn>;
|
||||
@@ -57,6 +59,8 @@ describe('MediaController', () => {
|
||||
deleteMedia: vi.fn(),
|
||||
getAvatar: vi.fn(),
|
||||
updateAvatar: vi.fn(),
|
||||
getUploadedMedia: vi.fn(),
|
||||
resolveMediaReference: vi.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -97,8 +101,8 @@ describe('MediaController', () => {
|
||||
}).compile();
|
||||
|
||||
controller = module.get<MediaController>(MediaController);
|
||||
service = module.get(MediaService) as any;
|
||||
generationService = module.get(MediaGenerationService) as any;
|
||||
service = module.get(MediaService) as never;
|
||||
generationService = module.get(MediaGenerationService) as never;
|
||||
});
|
||||
|
||||
const createMockResponse = (): Response => {
|
||||
@@ -375,6 +379,7 @@ describe('MediaController', () => {
|
||||
};
|
||||
const mockService = {
|
||||
getMedia: vi.fn().mockResolvedValue({ id: mediaId }),
|
||||
getUploadedMedia: vi.fn().mockResolvedValue({ bytes: pngBuffer, contentType: 'image/png' }),
|
||||
};
|
||||
|
||||
const module = await Test.createTestingModule({
|
||||
@@ -409,8 +414,7 @@ describe('MediaController', () => {
|
||||
await testController.getUploadedMedia(mediaId, res);
|
||||
|
||||
expect(mockService.getMedia).toHaveBeenCalledWith(mediaId);
|
||||
expect(mockStorage.getBytes).toHaveBeenCalledWith('uploaded/media-123');
|
||||
expect(mockStorage.getMetadata).toHaveBeenCalledWith('uploaded/media-123');
|
||||
expect(mockService.getUploadedMedia).toHaveBeenCalledWith('uploaded/media-123');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'image/png');
|
||||
expect(res.setHeader).toHaveBeenCalledWith('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
expect(res.status).toHaveBeenCalledWith(200);
|
||||
@@ -569,7 +573,7 @@ describe('MediaController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -603,6 +607,8 @@ describe('MediaController', () => {
|
||||
uploadMedia: vi.fn(),
|
||||
getAvatar: vi.fn(),
|
||||
updateAvatar: vi.fn(),
|
||||
getUploadedMedia: vi.fn(async () => ({ bytes: Buffer.from([]), contentType: 'image/png' })),
|
||||
resolveMediaReference: vi.fn(async () => '/path'),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -657,9 +663,9 @@ describe('MediaController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -19,9 +19,7 @@ import type { MulterFile } from './types/MulterFile';
|
||||
import { MediaGenerationService } from '@core/media/domain/services/MediaGenerationService';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter';
|
||||
import { LOGGER_TOKEN, MEDIA_STORAGE_PORT_TOKEN } from './MediaTokens';
|
||||
import type { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
|
||||
import { LOGGER_TOKEN } from './MediaTokens';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
@@ -36,8 +34,6 @@ export class MediaController {
|
||||
@Inject(MediaService) private readonly mediaService: MediaService,
|
||||
@Inject(MediaGenerationService) private readonly mediaGenerationService: MediaGenerationService,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
@Inject(MediaResolverAdapter) private readonly mediaResolver: MediaResolverAdapter,
|
||||
@Inject(MEDIA_STORAGE_PORT_TOKEN) private readonly mediaStorage: MediaStoragePort,
|
||||
) {}
|
||||
|
||||
@Post('avatar/generate')
|
||||
@@ -272,23 +268,19 @@ export class MediaController {
|
||||
// The mediaId is used as the storage key
|
||||
const storageKey = `uploaded/${mediaId}`;
|
||||
|
||||
// Get file bytes from storage
|
||||
const bytes = await this.mediaStorage.getBytes!(storageKey);
|
||||
if (!bytes) {
|
||||
// Get file bytes from storage via service
|
||||
const result = await this.mediaService.getUploadedMedia(storageKey);
|
||||
if (!result) {
|
||||
this.logger.warn('[MediaController] Uploaded media file not found', { mediaId, storageKey });
|
||||
res.status(HttpStatus.NOT_FOUND).json({ error: 'Media file not found' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get metadata to determine content type
|
||||
const metadata = await this.mediaStorage.getMetadata!(storageKey);
|
||||
const contentType = metadata?.contentType || 'application/octet-stream';
|
||||
|
||||
res.setHeader('Content-Type', contentType);
|
||||
res.setHeader('Content-Type', result.contentType);
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
res.status(HttpStatus.OK).send(bytes);
|
||||
res.status(HttpStatus.OK).send(result.bytes);
|
||||
|
||||
this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: bytes.length });
|
||||
this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: result.bytes.length });
|
||||
}
|
||||
|
||||
@Public()
|
||||
@@ -339,7 +331,7 @@ export class MediaController {
|
||||
|
||||
if (ref) {
|
||||
refHash = ref.hash();
|
||||
resolvedPath = await this.mediaResolver.resolve(ref);
|
||||
resolvedPath = await this.mediaService.resolveMediaReference(ref);
|
||||
|
||||
if (!resolvedPath) {
|
||||
notes.push('Resolver returned null');
|
||||
@@ -382,7 +374,7 @@ export class MediaController {
|
||||
): Promise<void> {
|
||||
this.logger.debug('[MediaController] Getting media details', { mediaId });
|
||||
const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId);
|
||||
|
||||
|
||||
if (dto) {
|
||||
this.logger.info('[MediaController] Media details found', { mediaId });
|
||||
res.status(HttpStatus.OK).json(dto);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { FaceValidationPort } from '@core/media/application/ports/FaceValidation
|
||||
import { AvatarGenerationPort } from '@core/media/application/ports/AvatarGenerationPort';
|
||||
import { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
|
||||
// Import use cases
|
||||
import { RequestAvatarGenerationUseCase } from '@core/media/application/use-cases/RequestAvatarGenerationUseCase';
|
||||
@@ -16,6 +17,8 @@ import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCa
|
||||
import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
import { ResolveMediaReferenceUseCase } from '@core/media/application/use-cases/ResolveMediaReferenceUseCase';
|
||||
import { GetUploadedMediaUseCase } from '@core/media/application/use-cases/GetUploadedMediaUseCase';
|
||||
|
||||
// Import presenters
|
||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||
@@ -39,6 +42,9 @@ import {
|
||||
DELETE_MEDIA_USE_CASE_TOKEN,
|
||||
GET_AVATAR_USE_CASE_TOKEN,
|
||||
UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||
RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
|
||||
GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
|
||||
MEDIA_RESOLVER_PORT_TOKEN,
|
||||
} from './MediaTokens';
|
||||
|
||||
export * from './MediaTokens';
|
||||
@@ -92,7 +98,7 @@ const initLogger = InitializationLogger.getInstance();
|
||||
export const MediaProviders: Provider[] = createLoggedProviders([
|
||||
MediaGenerationService,
|
||||
{
|
||||
provide: MediaResolverAdapter,
|
||||
provide: MEDIA_RESOLVER_PORT_TOKEN,
|
||||
useFactory: () => new MediaResolverAdapter({}),
|
||||
},
|
||||
RequestAvatarGenerationPresenter,
|
||||
@@ -156,4 +162,16 @@ export const MediaProviders: Provider[] = createLoggedProviders([
|
||||
new UpdateAvatarUseCase(avatarRepo, logger),
|
||||
inject: [AVATAR_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
], initLogger);
|
||||
{
|
||||
provide: RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
|
||||
useFactory: (mediaResolver: MediaResolverPort) =>
|
||||
new ResolveMediaReferenceUseCase(mediaResolver),
|
||||
inject: [MEDIA_RESOLVER_PORT_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
|
||||
useFactory: (mediaStorage: MediaStoragePort) =>
|
||||
new GetUploadedMediaUseCase(mediaStorage),
|
||||
inject: [MEDIA_STORAGE_PORT_TOKEN],
|
||||
},
|
||||
], initLogger);
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { ValidateFaceInputDTO } from './dtos/ValidateFaceInputDTO';
|
||||
import type { ValidateFaceOutputDTO } from './dtos/ValidateFaceOutputDTO';
|
||||
import type { RacingSuitColor } from '@core/media/domain/types/AvatarGenerationRequest';
|
||||
import type { MulterFile } from './types/MulterFile';
|
||||
import type { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
|
||||
type UploadMediaInput = UploadMediaInputDTO;
|
||||
@@ -24,6 +25,8 @@ import { GetMediaUseCase } from '@core/media/application/use-cases/GetMediaUseCa
|
||||
import { DeleteMediaUseCase } from '@core/media/application/use-cases/DeleteMediaUseCase';
|
||||
import { GetAvatarUseCase } from '@core/media/application/use-cases/GetAvatarUseCase';
|
||||
import { UpdateAvatarUseCase } from '@core/media/application/use-cases/UpdateAvatarUseCase';
|
||||
import { ResolveMediaReferenceUseCase } from '@core/media/application/use-cases/ResolveMediaReferenceUseCase';
|
||||
import { GetUploadedMediaUseCase, type GetUploadedMediaResult } from '@core/media/application/use-cases/GetUploadedMediaUseCase';
|
||||
|
||||
// Presenters (now transformers)
|
||||
import { RequestAvatarGenerationPresenter } from './presenters/RequestAvatarGenerationPresenter';
|
||||
@@ -40,6 +43,8 @@ import {
|
||||
DELETE_MEDIA_USE_CASE_TOKEN,
|
||||
GET_AVATAR_USE_CASE_TOKEN,
|
||||
UPDATE_AVATAR_USE_CASE_TOKEN,
|
||||
RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN,
|
||||
GET_UPLOADED_MEDIA_USE_CASE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
} from './MediaTokens';
|
||||
import type { Logger } from '@core/shared/application';
|
||||
@@ -59,6 +64,10 @@ export class MediaService {
|
||||
private readonly getAvatarUseCase: GetAvatarUseCase,
|
||||
@Inject(UPDATE_AVATAR_USE_CASE_TOKEN)
|
||||
private readonly updateAvatarUseCase: UpdateAvatarUseCase,
|
||||
@Inject(RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN)
|
||||
private readonly resolveMediaReferenceUseCase: ResolveMediaReferenceUseCase,
|
||||
@Inject(GET_UPLOADED_MEDIA_USE_CASE_TOKEN)
|
||||
private readonly getUploadedMediaUseCase: GetUploadedMediaUseCase,
|
||||
@Inject(LOGGER_TOKEN)
|
||||
private readonly logger: Logger,
|
||||
private readonly requestAvatarGenerationPresenter: RequestAvatarGenerationPresenter,
|
||||
@@ -211,4 +220,20 @@ export class MediaService {
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
}
|
||||
|
||||
async resolveMediaReference(reference: MediaReference): Promise<string | null> {
|
||||
const result = await this.resolveMediaReferenceUseCase.execute({ reference });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().message);
|
||||
}
|
||||
return result.unwrap();
|
||||
}
|
||||
|
||||
async getUploadedMedia(storageKey: string): Promise<GetUploadedMediaResult | null> {
|
||||
const result = await this.getUploadedMediaUseCase.execute({ storageKey });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().message);
|
||||
}
|
||||
return result.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,7 @@ export const UPLOAD_MEDIA_USE_CASE_TOKEN = 'UploadMediaUseCase';
|
||||
export const GET_MEDIA_USE_CASE_TOKEN = 'GetMediaUseCase';
|
||||
export const DELETE_MEDIA_USE_CASE_TOKEN = 'DeleteMediaUseCase';
|
||||
export const GET_AVATAR_USE_CASE_TOKEN = 'GetAvatarUseCase';
|
||||
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
|
||||
export const UPDATE_AVATAR_USE_CASE_TOKEN = 'UpdateAvatarUseCase';
|
||||
export const RESOLVE_MEDIA_REFERENCE_USE_CASE_TOKEN = 'ResolveMediaReferenceUseCase';
|
||||
export const GET_UPLOADED_MEDIA_USE_CASE_TOKEN = 'GetUploadedMediaUseCase';
|
||||
export const MEDIA_RESOLVER_PORT_TOKEN = 'MediaResolverPort';
|
||||
@@ -3,11 +3,12 @@ import { Module } from '@nestjs/common';
|
||||
import { NotificationsPersistenceModule } from '../../persistence/notifications/NotificationsPersistenceModule';
|
||||
import { NotificationsController } from './NotificationsController';
|
||||
import { NotificationsService } from './NotificationsService';
|
||||
import { NotificationsProviders } from './NotificationsProviders';
|
||||
|
||||
@Module({
|
||||
imports: [NotificationsPersistenceModule],
|
||||
controllers: [NotificationsController],
|
||||
providers: [NotificationsService],
|
||||
providers: [NotificationsService, ...NotificationsProviders],
|
||||
exports: [NotificationsPersistenceModule, NotificationsService],
|
||||
})
|
||||
export class NotificationsModule {}
|
||||
export class NotificationsModule {}
|
||||
|
||||
36
apps/api/src/domain/notifications/NotificationsProviders.ts
Normal file
36
apps/api/src/domain/notifications/NotificationsProviders.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import { GetUnreadNotificationsUseCase } from '@core/notifications/application/use-cases/GetUnreadNotificationsUseCase';
|
||||
import { GetAllNotificationsUseCase } from '@core/notifications/application/use-cases/GetAllNotificationsUseCase';
|
||||
import { MarkNotificationReadUseCase } from '@core/notifications/application/use-cases/MarkNotificationReadUseCase';
|
||||
import { INotificationRepository } from '@core/notifications/domain/repositories/INotificationRepository';
|
||||
import { NOTIFICATION_REPOSITORY_TOKEN } from '../../persistence/notifications/NotificationsPersistenceTokens';
|
||||
import {
|
||||
GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN,
|
||||
GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN,
|
||||
MARK_NOTIFICATION_READ_USE_CASE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
} from './NotificationsTokens';
|
||||
|
||||
export const NotificationsProviders: Provider[] = [
|
||||
{
|
||||
provide: LOGGER_TOKEN,
|
||||
useClass: ConsoleLogger,
|
||||
},
|
||||
{
|
||||
provide: GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN,
|
||||
useFactory: (repo: INotificationRepository, logger: Logger) => new GetUnreadNotificationsUseCase(repo, logger),
|
||||
inject: [NOTIFICATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN,
|
||||
useFactory: (repo: INotificationRepository, logger: Logger) => new GetAllNotificationsUseCase(repo, logger),
|
||||
inject: [NOTIFICATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: MARK_NOTIFICATION_READ_USE_CASE_TOKEN,
|
||||
useFactory: (repo: INotificationRepository, logger: Logger) => new MarkNotificationReadUseCase(repo, logger),
|
||||
inject: [NOTIFICATION_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
@@ -1,35 +1,48 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { INotificationRepository } from '@core/notifications/domain/repositories/INotificationRepository';
|
||||
import { NOTIFICATION_REPOSITORY_TOKEN } from '../../persistence/notifications/NotificationsPersistenceTokens';
|
||||
import { GetUnreadNotificationsUseCase } from '@core/notifications/application/use-cases/GetUnreadNotificationsUseCase';
|
||||
import { GetAllNotificationsUseCase } from '@core/notifications/application/use-cases/GetAllNotificationsUseCase';
|
||||
import { MarkNotificationReadUseCase } from '@core/notifications/application/use-cases/MarkNotificationReadUseCase';
|
||||
import {
|
||||
GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN,
|
||||
GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN,
|
||||
MARK_NOTIFICATION_READ_USE_CASE_TOKEN,
|
||||
} from './NotificationsTokens';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsService {
|
||||
constructor(
|
||||
@Inject(NOTIFICATION_REPOSITORY_TOKEN)
|
||||
private readonly notificationRepository: INotificationRepository,
|
||||
@Inject(GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN)
|
||||
private readonly getUnreadNotificationsUseCase: GetUnreadNotificationsUseCase,
|
||||
@Inject(GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN)
|
||||
private readonly getAllNotificationsUseCase: GetAllNotificationsUseCase,
|
||||
@Inject(MARK_NOTIFICATION_READ_USE_CASE_TOKEN)
|
||||
private readonly markNotificationReadUseCase: MarkNotificationReadUseCase,
|
||||
) {}
|
||||
|
||||
async getUnreadNotifications(userId: string): Promise<Array<Record<string, unknown>>> {
|
||||
const notifications = await this.notificationRepository.findUnreadByRecipientId(userId);
|
||||
return notifications.map(n => n.toJSON() as Record<string, unknown>);
|
||||
const result = await this.getUnreadNotificationsUseCase.execute({ recipientId: userId });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message || 'Failed to get unread notifications');
|
||||
}
|
||||
return result.unwrap().notifications.map(n => n.toJSON() as Record<string, unknown>);
|
||||
}
|
||||
|
||||
async getAllNotifications(userId: string): Promise<Array<Record<string, unknown>>> {
|
||||
const notifications = await this.notificationRepository.findByRecipientId(userId);
|
||||
return notifications.map(n => n.toJSON() as Record<string, unknown>);
|
||||
const result = await this.getAllNotificationsUseCase.execute({ recipientId: userId });
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message || 'Failed to get all notifications');
|
||||
}
|
||||
return result.unwrap().notifications.map(n => n.toJSON() as Record<string, unknown>);
|
||||
}
|
||||
|
||||
async markAsRead(notificationId: string, userId: string): Promise<void> {
|
||||
const notification = await this.notificationRepository.findById(notificationId);
|
||||
if (!notification) {
|
||||
throw new Error('Notification not found');
|
||||
}
|
||||
const result = await this.markNotificationReadUseCase.execute({
|
||||
notificationId,
|
||||
recipientId: userId,
|
||||
});
|
||||
|
||||
if (notification.recipientId !== userId) {
|
||||
throw new Error('Unauthorized');
|
||||
if (result.isErr()) {
|
||||
throw new Error(result.unwrapErr().details?.message || 'Failed to mark notification as read');
|
||||
}
|
||||
|
||||
const updated = notification.markAsRead();
|
||||
await this.notificationRepository.update(updated);
|
||||
}
|
||||
}
|
||||
|
||||
4
apps/api/src/domain/notifications/NotificationsTokens.ts
Normal file
4
apps/api/src/domain/notifications/NotificationsTokens.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN = Symbol('GET_UNREAD_NOTIFICATIONS_USE_CASE_TOKEN');
|
||||
export const GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN = Symbol('GET_ALL_NOTIFICATIONS_USE_CASE_TOKEN');
|
||||
export const MARK_NOTIFICATION_READ_USE_CASE_TOKEN = Symbol('MARK_NOTIFICATION_READ_USE_CASE_TOKEN');
|
||||
export const LOGGER_TOKEN = Symbol('LOGGER_TOKEN');
|
||||
@@ -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();
|
||||
|
||||
@@ -377,7 +377,7 @@ describe('PaymentsController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -415,9 +415,9 @@ describe('PaymentsController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -5,7 +5,9 @@ import { PaymentsService } from './PaymentsService';
|
||||
describe('PaymentsService', () => {
|
||||
const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
|
||||
|
||||
function makeService(overrides?: Partial<Record<string, any>>) {
|
||||
type MockUseCase = { execute: ReturnType<typeof vi.fn> };
|
||||
|
||||
function makeService(overrides?: Partial<Record<string, MockUseCase>>) {
|
||||
const getPaymentsUseCase = overrides?.getPaymentsUseCase ?? { execute: vi.fn(async () => Result.ok({ payments: [] })) };
|
||||
const createPaymentUseCase = overrides?.createPaymentUseCase ?? { execute: vi.fn(async () => Result.ok({ paymentId: 'p1' })) };
|
||||
const updatePaymentStatusUseCase =
|
||||
@@ -25,19 +27,19 @@ describe('PaymentsService', () => {
|
||||
overrides?.processWalletTransactionUseCase ?? { execute: vi.fn(async () => Result.ok({ success: true })) };
|
||||
|
||||
const service = new PaymentsService(
|
||||
getPaymentsUseCase as any,
|
||||
createPaymentUseCase as any,
|
||||
updatePaymentStatusUseCase as any,
|
||||
getMembershipFeesUseCase as any,
|
||||
upsertMembershipFeeUseCase as any,
|
||||
updateMemberPaymentUseCase as any,
|
||||
getPrizesUseCase as any,
|
||||
createPrizeUseCase as any,
|
||||
awardPrizeUseCase as any,
|
||||
deletePrizeUseCase as any,
|
||||
getWalletUseCase as any,
|
||||
processWalletTransactionUseCase as any,
|
||||
logger as any,
|
||||
getPaymentsUseCase as never,
|
||||
createPaymentUseCase as never,
|
||||
updatePaymentStatusUseCase as never,
|
||||
getMembershipFeesUseCase as never,
|
||||
upsertMembershipFeeUseCase as never,
|
||||
updateMemberPaymentUseCase as never,
|
||||
getPrizesUseCase as never,
|
||||
createPrizeUseCase as never,
|
||||
awardPrizeUseCase as never,
|
||||
deletePrizeUseCase as never,
|
||||
getWalletUseCase as never,
|
||||
processWalletTransactionUseCase as never,
|
||||
logger as never,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -59,7 +61,7 @@ describe('PaymentsService', () => {
|
||||
|
||||
it('getPayments returns presenter model on success', async () => {
|
||||
const { service, getPaymentsUseCase } = makeService();
|
||||
await expect(service.getPayments({ leagueId: 'l1' } as any)).resolves.toEqual({ payments: [] });
|
||||
await expect(service.getPayments({ leagueId: 'l1' } as never)).resolves.toEqual({ payments: [] });
|
||||
expect(getPaymentsUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
@@ -67,12 +69,12 @@ describe('PaymentsService', () => {
|
||||
const { service } = makeService({
|
||||
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
|
||||
});
|
||||
await expect(service.getPayments({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
});
|
||||
|
||||
it('createPayment returns presenter model on success', async () => {
|
||||
const { service, createPaymentUseCase } = makeService();
|
||||
await expect(service.createPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ paymentId: 'p1' });
|
||||
await expect(service.createPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ paymentId: 'p1' });
|
||||
expect(createPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
@@ -80,12 +82,12 @@ describe('PaymentsService', () => {
|
||||
const { service } = makeService({
|
||||
createPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
|
||||
});
|
||||
await expect(service.createPayment({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
});
|
||||
|
||||
it('updatePaymentStatus returns presenter model on success', async () => {
|
||||
const { service, updatePaymentStatusUseCase } = makeService();
|
||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(updatePaymentStatusUseCase.execute).toHaveBeenCalledWith({ paymentId: 'p1' });
|
||||
});
|
||||
|
||||
@@ -93,7 +95,7 @@ describe('PaymentsService', () => {
|
||||
const { service } = makeService({
|
||||
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
|
||||
});
|
||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
});
|
||||
|
||||
it('getMembershipFees returns viewModel on success', async () => {
|
||||
@@ -101,7 +103,7 @@ describe('PaymentsService', () => {
|
||||
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.ok({ fee: { amount: 1 }, payments: [] })) }
|
||||
});
|
||||
|
||||
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).resolves.toEqual({
|
||||
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).resolves.toEqual({
|
||||
fee: { amount: 1 },
|
||||
payments: [],
|
||||
});
|
||||
@@ -112,22 +114,22 @@ describe('PaymentsService', () => {
|
||||
const { service } = makeService({
|
||||
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
|
||||
});
|
||||
await expect(service.getMembershipFees({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
await expect(service.getMembershipFees({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
});
|
||||
|
||||
it('upsertMembershipFee returns viewModel on success', async () => {
|
||||
const { service, upsertMembershipFeeUseCase } = makeService();
|
||||
|
||||
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(upsertMembershipFeeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
it('upsertMembershipFee throws on error branch (defensive check)', async () => {
|
||||
const { service } = makeService({
|
||||
upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as any)) },
|
||||
upsertMembershipFeeUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' } as never)) },
|
||||
});
|
||||
|
||||
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as any)).rejects.toThrow(
|
||||
await expect(service.upsertMembershipFee({ leagueId: 'l1' } as never)).rejects.toThrow(
|
||||
'Failed to upsert membership fee',
|
||||
);
|
||||
});
|
||||
@@ -135,7 +137,7 @@ describe('PaymentsService', () => {
|
||||
it('updateMemberPayment returns viewModel on success', async () => {
|
||||
const { service, updateMemberPaymentUseCase } = makeService();
|
||||
|
||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(updateMemberPaymentUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
@@ -144,7 +146,7 @@ describe('PaymentsService', () => {
|
||||
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({ code: 'REPOSITORY_ERROR' })) },
|
||||
});
|
||||
|
||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('REPOSITORY_ERROR');
|
||||
});
|
||||
|
||||
it('getPrizes maps seasonId optional', async () => {
|
||||
@@ -153,10 +155,10 @@ describe('PaymentsService', () => {
|
||||
getPrizesUseCase,
|
||||
});
|
||||
|
||||
await expect(service.getPrizes({ leagueId: 'l1' } as any)).resolves.toEqual({ prizes: [] });
|
||||
await expect(service.getPrizes({ leagueId: 'l1' } as never)).resolves.toEqual({ prizes: [] });
|
||||
expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
|
||||
await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as any)).resolves.toEqual({ prizes: [] });
|
||||
await expect(service.getPrizes({ leagueId: 'l1', seasonId: 's1' } as never)).resolves.toEqual({ prizes: [] });
|
||||
expect(getPrizesUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1', seasonId: 's1' });
|
||||
});
|
||||
|
||||
@@ -166,7 +168,7 @@ describe('PaymentsService', () => {
|
||||
createPrizeUseCase,
|
||||
});
|
||||
|
||||
await expect(service.createPrize({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.createPrize({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(createPrizeUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
@@ -176,7 +178,7 @@ describe('PaymentsService', () => {
|
||||
awardPrizeUseCase,
|
||||
});
|
||||
|
||||
await expect(service.awardPrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.awardPrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(awardPrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
|
||||
});
|
||||
|
||||
@@ -186,7 +188,7 @@ describe('PaymentsService', () => {
|
||||
deletePrizeUseCase,
|
||||
});
|
||||
|
||||
await expect(service.deletePrize({ prizeId: 'p1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.deletePrize({ prizeId: 'p1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(deletePrizeUseCase.execute).toHaveBeenCalledWith({ prizeId: 'p1' });
|
||||
});
|
||||
|
||||
@@ -196,7 +198,7 @@ describe('PaymentsService', () => {
|
||||
getWalletUseCase,
|
||||
});
|
||||
|
||||
await expect(service.getWallet({ leagueId: 'l1' } as any)).resolves.toEqual({ balance: 10 });
|
||||
await expect(service.getWallet({ leagueId: 'l1' } as never)).resolves.toEqual({ balance: 10 });
|
||||
expect(getWalletUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
@@ -206,47 +208,47 @@ describe('PaymentsService', () => {
|
||||
processWalletTransactionUseCase,
|
||||
});
|
||||
|
||||
await expect(service.processWalletTransaction({ leagueId: 'l1' } as any)).resolves.toEqual({ success: true });
|
||||
await expect(service.processWalletTransaction({ leagueId: 'l1' } as never)).resolves.toEqual({ success: true });
|
||||
expect(processWalletTransactionUseCase.execute).toHaveBeenCalledWith({ leagueId: 'l1' });
|
||||
});
|
||||
|
||||
it('getPayments throws fallback message when error has no code', async () => {
|
||||
const { service } = makeService({
|
||||
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
|
||||
getPaymentsUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
|
||||
});
|
||||
|
||||
await expect(service.getPayments({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to get payments');
|
||||
await expect(service.getPayments({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to get payments');
|
||||
});
|
||||
|
||||
it('createPayment throws fallback message when error has no code', async () => {
|
||||
const { service } = makeService({
|
||||
createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
|
||||
createPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
|
||||
});
|
||||
|
||||
await expect(service.createPayment({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to create payment');
|
||||
await expect(service.createPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to create payment');
|
||||
});
|
||||
|
||||
it('updatePaymentStatus throws fallback message when error has no code', async () => {
|
||||
const { service } = makeService({
|
||||
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
|
||||
updatePaymentStatusUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
|
||||
});
|
||||
|
||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as any)).rejects.toThrow('Failed to update payment status');
|
||||
await expect(service.updatePaymentStatus({ paymentId: 'p1' } as never)).rejects.toThrow('Failed to update payment status');
|
||||
});
|
||||
|
||||
it('getMembershipFees throws fallback message when error has no code', async () => {
|
||||
const { service } = makeService({
|
||||
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
|
||||
getMembershipFeesUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
|
||||
});
|
||||
|
||||
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as any)).rejects.toThrow('Failed to get membership fees');
|
||||
await expect(service.getMembershipFees({ leagueId: 'l1', driverId: 'd1' } as never)).rejects.toThrow('Failed to get membership fees');
|
||||
});
|
||||
|
||||
it('updateMemberPayment throws fallback message when error has no code', async () => {
|
||||
const { service } = makeService({
|
||||
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as any)) },
|
||||
updateMemberPaymentUseCase: { execute: vi.fn(async () => Result.err({} as never)) },
|
||||
});
|
||||
|
||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as any)).rejects.toThrow('Failed to update member payment');
|
||||
await expect(service.updateMemberPayment({ leagueId: 'l1' } as never)).rejects.toThrow('Failed to update member payment');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -58,8 +58,8 @@ describe('RaceController', () => {
|
||||
|
||||
describe('getAllRaces', () => {
|
||||
it('should return all races view model', async () => {
|
||||
const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as any;
|
||||
service.getAllRaces.mockResolvedValue(mockPresenter);
|
||||
const mockPresenter = { viewModel: { races: [], filters: { statuses: [], leagues: [] } } } as unknown as { viewModel: unknown };
|
||||
service.getAllRaces.mockResolvedValue(mockPresenter as never);
|
||||
|
||||
const result = await controller.getAllRaces();
|
||||
|
||||
@@ -70,8 +70,8 @@ describe('RaceController', () => {
|
||||
|
||||
describe('getTotalRaces', () => {
|
||||
it('should return total races count view model', async () => {
|
||||
const mockPresenter = { viewModel: { totalRaces: 5 } } as any;
|
||||
service.getTotalRaces.mockResolvedValue(mockPresenter);
|
||||
const mockPresenter = { viewModel: { totalRaces: 5 } } as unknown as { viewModel: unknown };
|
||||
service.getTotalRaces.mockResolvedValue(mockPresenter as never);
|
||||
|
||||
const result = await controller.getTotalRaces();
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('RaceController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -120,9 +120,9 @@ describe('RaceController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('TeamController', () => {
|
||||
const result = { team: { id: teamId, name: 'Team', tag: 'TAG', description: 'Desc', ownerId: 'owner', leagues: [], isRecruiting: false }, membership: null, canManage: false };
|
||||
service.getDetails.mockResolvedValue(result);
|
||||
|
||||
const mockReq = { user: { userId } } as any;
|
||||
const mockReq = { user: { userId } } as never;
|
||||
|
||||
const response = await controller.getDetails(teamId, mockReq);
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('TeamController', () => {
|
||||
const result = { id: 'team-456', success: true };
|
||||
service.create.mockResolvedValue(result);
|
||||
|
||||
const mockReq = { user: { userId } } as any;
|
||||
const mockReq = { user: { userId } } as never;
|
||||
|
||||
const response = await controller.create(input, mockReq);
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('TeamController', () => {
|
||||
const result = { success: true };
|
||||
service.update.mockResolvedValue(result);
|
||||
|
||||
const mockReq = { user: { userId } } as any;
|
||||
const mockReq = { user: { userId } } as never;
|
||||
|
||||
const response = await controller.update(teamId, input, mockReq);
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('TeamController', () => {
|
||||
});
|
||||
|
||||
describe('auth guards (HTTP)', () => {
|
||||
let app: any;
|
||||
let app: import("@nestjs/common").INestApplication;
|
||||
|
||||
const sessionPort: { getCurrentSession: () => Promise<null | { token: string; user: { id: string } }> } = {
|
||||
getCurrentSession: vi.fn(async () => null),
|
||||
@@ -196,9 +196,9 @@ describe('TeamController', () => {
|
||||
|
||||
const reflector = new Reflector();
|
||||
app.useGlobalGuards(
|
||||
new AuthenticationGuard(sessionPort as any),
|
||||
new AuthorizationGuard(reflector, authorizationService as any),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as any),
|
||||
new AuthenticationGuard(sessionPort as never),
|
||||
new AuthorizationGuard(reflector, authorizationService as never),
|
||||
new FeatureAvailabilityGuard(reflector, policyService as never),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import { Provider } from '@nestjs/common';
|
||||
|
||||
import { IMAGE_SERVICE_TOKEN, LOGGER_TOKEN, MEDIA_REPOSITORY_TOKEN, MEDIA_RESOLVER_TOKEN } from './TeamTokens';
|
||||
import {
|
||||
IMAGE_SERVICE_TOKEN,
|
||||
LOGGER_TOKEN,
|
||||
MEDIA_REPOSITORY_TOKEN,
|
||||
MEDIA_RESOLVER_TOKEN,
|
||||
TEAM_REPOSITORY_TOKEN,
|
||||
TEAM_MEMBERSHIP_REPOSITORY_TOKEN,
|
||||
DRIVER_REPOSITORY_TOKEN,
|
||||
TEAM_STATS_REPOSITORY_TOKEN,
|
||||
GET_ALL_TEAMS_USE_CASE_TOKEN,
|
||||
GET_TEAM_DETAILS_USE_CASE_TOKEN,
|
||||
GET_TEAM_MEMBERS_USE_CASE_TOKEN,
|
||||
GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
|
||||
CREATE_TEAM_USE_CASE_TOKEN,
|
||||
UPDATE_TEAM_USE_CASE_TOKEN,
|
||||
GET_DRIVER_TEAM_USE_CASE_TOKEN,
|
||||
GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
|
||||
JOIN_TEAM_USE_CASE_TOKEN,
|
||||
} from './TeamTokens';
|
||||
|
||||
export {
|
||||
TEAM_REPOSITORY_TOKEN,
|
||||
@@ -15,6 +33,10 @@ export {
|
||||
// Import core interfaces
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
||||
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
|
||||
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||
|
||||
// Import concrete implementations
|
||||
import { InMemoryImageServiceAdapter } from '@adapters/media/ports/InMemoryImageServiceAdapter';
|
||||
@@ -22,6 +44,17 @@ import { ConsoleLogger } from '@adapters/logging/ConsoleLogger';
|
||||
import { InMemoryMediaRepository } from '@adapters/racing/persistence/media/InMemoryMediaRepository';
|
||||
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter';
|
||||
|
||||
// Import use cases
|
||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
import { GetTeamDetailsUseCase } from '@core/racing/application/use-cases/GetTeamDetailsUseCase';
|
||||
import { GetTeamMembersUseCase } from '@core/racing/application/use-cases/GetTeamMembersUseCase';
|
||||
import { GetTeamJoinRequestsUseCase } from '@core/racing/application/use-cases/GetTeamJoinRequestsUseCase';
|
||||
import { CreateTeamUseCase } from '@core/racing/application/use-cases/CreateTeamUseCase';
|
||||
import { UpdateTeamUseCase } from '@core/racing/application/use-cases/UpdateTeamUseCase';
|
||||
import { GetDriverTeamUseCase } from '@core/racing/application/use-cases/GetDriverTeamUseCase';
|
||||
import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/GetTeamMembershipUseCase';
|
||||
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
|
||||
|
||||
// Import presenters
|
||||
import { AllTeamsPresenter } from './presenters/AllTeamsPresenter';
|
||||
|
||||
@@ -69,4 +102,59 @@ export const TeamProviders: Provider[] = [
|
||||
},
|
||||
inject: [MEDIA_RESOLVER_TOKEN],
|
||||
},
|
||||
];
|
||||
// Use Cases
|
||||
{
|
||||
provide: GET_ALL_TEAMS_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, statsRepo: ITeamStatsRepository, logger: Logger) =>
|
||||
new GetAllTeamsUseCase(teamRepo, membershipRepo, statsRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, TEAM_STATS_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_TEAM_DETAILS_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
|
||||
new GetTeamDetailsUseCase(teamRepo, membershipRepo),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_TEAM_MEMBERS_USE_CASE_TOKEN,
|
||||
useFactory: (membershipRepo: ITeamMembershipRepository, driverRepo: IDriverRepository, teamRepo: ITeamRepository, logger: Logger) =>
|
||||
new GetTeamMembersUseCase(membershipRepo, driverRepo, teamRepo, logger),
|
||||
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, TEAM_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
|
||||
useFactory: (membershipRepo: ITeamMembershipRepository, driverRepo: IDriverRepository, teamRepo: ITeamRepository) =>
|
||||
new GetTeamJoinRequestsUseCase(membershipRepo, driverRepo, teamRepo),
|
||||
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, TEAM_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: CREATE_TEAM_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new CreateTeamUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: UPDATE_TEAM_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository) =>
|
||||
new UpdateTeamUseCase(teamRepo, membershipRepo),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_DRIVER_TEAM_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetDriverTeamUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
|
||||
useFactory: (membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new GetTeamMembershipUseCase(membershipRepo, logger),
|
||||
inject: [TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
{
|
||||
provide: JOIN_TEAM_USE_CASE_TOKEN,
|
||||
useFactory: (teamRepo: ITeamRepository, membershipRepo: ITeamMembershipRepository, logger: Logger) =>
|
||||
new JoinTeamUseCase(teamRepo, membershipRepo, logger),
|
||||
inject: [TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, LOGGER_TOKEN],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ import { GetTeamMembershipOutputDTO } from './dtos/GetTeamMembershipOutputDTO';
|
||||
|
||||
// Core imports
|
||||
import type { Logger } from '@core/shared/application/Logger';
|
||||
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
|
||||
import type { ITeamMembershipRepository } from '@core/racing/domain/repositories/ITeamMembershipRepository';
|
||||
import type { IDriverRepository } from '@core/racing/domain/repositories/IDriverRepository';
|
||||
|
||||
// Use cases
|
||||
import { GetAllTeamsUseCase } from '@core/racing/application/use-cases/GetAllTeamsUseCase';
|
||||
@@ -28,39 +25,44 @@ import { GetTeamMembershipUseCase } from '@core/racing/application/use-cases/Get
|
||||
import { JoinTeamUseCase } from '@core/racing/application/use-cases/JoinTeamUseCase';
|
||||
|
||||
// Tokens
|
||||
import { TEAM_REPOSITORY_TOKEN, TEAM_MEMBERSHIP_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LOGGER_TOKEN, TEAM_STATS_REPOSITORY_TOKEN } from './TeamTokens';
|
||||
import type { ITeamStatsRepository } from '@core/racing/domain/repositories/ITeamStatsRepository';
|
||||
import {
|
||||
LOGGER_TOKEN,
|
||||
GET_ALL_TEAMS_USE_CASE_TOKEN,
|
||||
GET_TEAM_DETAILS_USE_CASE_TOKEN,
|
||||
GET_TEAM_MEMBERS_USE_CASE_TOKEN,
|
||||
GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN,
|
||||
CREATE_TEAM_USE_CASE_TOKEN,
|
||||
UPDATE_TEAM_USE_CASE_TOKEN,
|
||||
GET_DRIVER_TEAM_USE_CASE_TOKEN,
|
||||
GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN,
|
||||
JOIN_TEAM_USE_CASE_TOKEN,
|
||||
} from './TeamTokens';
|
||||
|
||||
@Injectable()
|
||||
export class TeamService {
|
||||
constructor(
|
||||
@Inject(TEAM_REPOSITORY_TOKEN) private readonly teamRepository: ITeamRepository,
|
||||
@Inject(TEAM_MEMBERSHIP_REPOSITORY_TOKEN) private readonly membershipRepository: ITeamMembershipRepository,
|
||||
@Inject(DRIVER_REPOSITORY_TOKEN) private readonly driverRepository: IDriverRepository,
|
||||
@Inject(GET_ALL_TEAMS_USE_CASE_TOKEN) private readonly getAllTeamsUseCase: GetAllTeamsUseCase,
|
||||
@Inject(GET_TEAM_DETAILS_USE_CASE_TOKEN) private readonly getTeamDetailsUseCase: GetTeamDetailsUseCase,
|
||||
@Inject(GET_TEAM_MEMBERS_USE_CASE_TOKEN) private readonly getTeamMembersUseCase: GetTeamMembersUseCase,
|
||||
@Inject(GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN) private readonly getTeamJoinRequestsUseCase: GetTeamJoinRequestsUseCase,
|
||||
@Inject(CREATE_TEAM_USE_CASE_TOKEN) private readonly createTeamUseCase: CreateTeamUseCase,
|
||||
@Inject(UPDATE_TEAM_USE_CASE_TOKEN) private readonly updateTeamUseCase: UpdateTeamUseCase,
|
||||
@Inject(GET_DRIVER_TEAM_USE_CASE_TOKEN) private readonly getDriverTeamUseCase: GetDriverTeamUseCase,
|
||||
@Inject(GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN) private readonly getTeamMembershipUseCase: GetTeamMembershipUseCase,
|
||||
@Inject(JOIN_TEAM_USE_CASE_TOKEN) private readonly joinTeamUseCase: JoinTeamUseCase,
|
||||
@Inject(LOGGER_TOKEN) private readonly logger: Logger,
|
||||
@Inject(TEAM_STATS_REPOSITORY_TOKEN) private readonly teamStatsRepository: ITeamStatsRepository,
|
||||
) {}
|
||||
|
||||
async getAll(): Promise<GetAllTeamsOutputDTO> {
|
||||
this.logger.debug('[TeamService] Fetching all teams.');
|
||||
|
||||
const useCase = new GetAllTeamsUseCase(
|
||||
this.teamRepository,
|
||||
this.membershipRepository,
|
||||
this.teamStatsRepository,
|
||||
this.logger
|
||||
);
|
||||
const result = await useCase.execute({});
|
||||
const result = await this.getAllTeamsUseCase.execute({});
|
||||
if (result.isErr()) {
|
||||
this.logger.error('Error fetching all teams', new Error(result.error?.details?.message || 'Unknown error'));
|
||||
return { teams: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
this.logger.error('Error fetching all teams', new Error(result.unwrapErr().details?.message || 'Unknown error'));
|
||||
return { teams: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
return {
|
||||
teams: value.teams.map(t => ({
|
||||
id: t.team.id,
|
||||
@@ -86,18 +88,13 @@ export class TeamService {
|
||||
async getDetails(teamId: string, userId?: string): Promise<GetTeamDetailsOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching team details for teamId: ${teamId}, userId: ${userId}`);
|
||||
|
||||
const useCase = new GetTeamDetailsUseCase(this.teamRepository, this.membershipRepository);
|
||||
const result = await useCase.execute({ teamId, driverId: userId || '' });
|
||||
const result = await this.getTeamDetailsUseCase.execute({ teamId, driverId: userId || '' });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
this.logger.error(`Error fetching team details for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
// Convert to DTO
|
||||
return {
|
||||
team: {
|
||||
@@ -123,21 +120,9 @@ export class TeamService {
|
||||
async getMembers(teamId: string): Promise<GetTeamMembersOutputDTO> {
|
||||
this.logger.debug(`[TeamService] Fetching team members for teamId: ${teamId}`);
|
||||
|
||||
const useCase = new GetTeamMembersUseCase(this.membershipRepository, this.driverRepository, this.teamRepository, this.logger);
|
||||
const result = await useCase.execute({ teamId });
|
||||
const result = await this.getTeamMembersUseCase.execute({ teamId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return {
|
||||
members: [],
|
||||
totalCount: 0,
|
||||
ownerCount: 0,
|
||||
managerCount: 0,
|
||||
memberCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
this.logger.error(`Error fetching team members for teamId: ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
|
||||
return {
|
||||
members: [],
|
||||
totalCount: 0,
|
||||
@@ -147,6 +132,7 @@ export class TeamService {
|
||||
};
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
return {
|
||||
members: value.members.map(m => ({
|
||||
driverId: m.driver?.id || '',
|
||||
@@ -166,19 +152,9 @@ export class TeamService {
|
||||
async getJoinRequests(teamId: string): Promise<GetTeamJoinRequestsOutputDTO> {
|
||||
this.logger.debug(`[TeamService] Fetching team join requests for teamId: ${teamId}`);
|
||||
|
||||
const useCase = new GetTeamJoinRequestsUseCase(this.membershipRepository, this.driverRepository, this.teamRepository);
|
||||
const result = await useCase.execute({ teamId });
|
||||
const result = await this.getTeamJoinRequestsUseCase.execute({ teamId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.error?.details?.message || 'Unknown error'));
|
||||
return {
|
||||
requests: [],
|
||||
pendingCount: 0,
|
||||
totalCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
this.logger.error(`Error fetching team join requests for teamId: ${teamId}`, new Error(result.unwrapErr().details?.message || 'Unknown error'));
|
||||
return {
|
||||
requests: [],
|
||||
pendingCount: 0,
|
||||
@@ -186,6 +162,7 @@ export class TeamService {
|
||||
};
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
return {
|
||||
requests: value.joinRequests.map(r => ({
|
||||
requestId: r.id,
|
||||
@@ -212,18 +189,13 @@ export class TeamService {
|
||||
leagues: [],
|
||||
};
|
||||
|
||||
const useCase = new CreateTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute(command);
|
||||
const result = await this.createTeamUseCase.execute(command);
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error creating team: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return { id: '', success: false };
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
this.logger.error(`Error creating team: ${result.unwrapErr().details?.message || 'Unknown error'}`);
|
||||
return { id: '', success: false };
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
return { id: value.team.id, success: true };
|
||||
}
|
||||
|
||||
@@ -240,10 +212,9 @@ export class TeamService {
|
||||
updatedBy: userId || '',
|
||||
};
|
||||
|
||||
const useCase = new UpdateTeamUseCase(this.teamRepository, this.membershipRepository);
|
||||
const result = await useCase.execute(command);
|
||||
const result = await this.updateTeamUseCase.execute(command);
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error updating team ${teamId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
this.logger.error(`Error updating team ${teamId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
@@ -253,14 +224,13 @@ export class TeamService {
|
||||
async getDriverTeam(driverId: string): Promise<GetDriverTeamOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching team for driverId: ${driverId}`);
|
||||
|
||||
const useCase = new GetDriverTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute({ driverId });
|
||||
const result = await this.getDriverTeamUseCase.execute({ driverId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
this.logger.error(`Error fetching team for driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
const value = result.unwrap();
|
||||
if (!value || !value.team) {
|
||||
return null;
|
||||
}
|
||||
@@ -290,18 +260,13 @@ export class TeamService {
|
||||
async getMembership(teamId: string, driverId: string): Promise<GetTeamMembershipOutputDTO | null> {
|
||||
this.logger.debug(`[TeamService] Fetching team membership for teamId: ${teamId}, driverId: ${driverId}`);
|
||||
|
||||
const useCase = new GetTeamMembershipUseCase(this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute({ teamId, driverId });
|
||||
const result = await this.getTeamMembershipUseCase.execute({ teamId, driverId });
|
||||
if (result.isErr()) {
|
||||
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.error?.details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = result.value;
|
||||
if (!value) {
|
||||
this.logger.error(`Error fetching team membership for teamId: ${teamId}, driverId: ${driverId}: ${result.unwrapErr().details?.message || 'Unknown error'}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = result.unwrap();
|
||||
return value.membership ? {
|
||||
role: value.membership.role,
|
||||
joinedAt: value.membership.joinedAt,
|
||||
@@ -316,10 +281,9 @@ export class TeamService {
|
||||
throw new Error('User ID is required');
|
||||
}
|
||||
|
||||
const useCase = new JoinTeamUseCase(this.teamRepository, this.membershipRepository, this.logger);
|
||||
const result = await useCase.execute({ teamId, driverId: userId });
|
||||
const result = await this.joinTeamUseCase.execute({ teamId, driverId: userId });
|
||||
if (result.isErr()) {
|
||||
const error = result.error;
|
||||
const error = result.unwrapErr();
|
||||
this.logger.error(`Error joining team ${teamId}: ${error?.details?.message || 'Unknown error'}`);
|
||||
return { success: false, error: error?.details?.message || 'Unknown error' };
|
||||
}
|
||||
|
||||
@@ -6,4 +6,14 @@ export const LOGGER_TOKEN = 'Logger';
|
||||
export const TEAM_STATS_REPOSITORY_TOKEN = 'ITeamStatsRepository';
|
||||
export const MEDIA_REPOSITORY_TOKEN = 'IMediaRepository';
|
||||
export const MEDIA_RESOLVER_TOKEN = 'MediaResolverPort';
|
||||
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
|
||||
export const RESULT_REPOSITORY_TOKEN = 'IResultRepository';
|
||||
|
||||
export const GET_ALL_TEAMS_USE_CASE_TOKEN = Symbol('GET_ALL_TEAMS_USE_CASE_TOKEN');
|
||||
export const GET_TEAM_DETAILS_USE_CASE_TOKEN = Symbol('GET_TEAM_DETAILS_USE_CASE_TOKEN');
|
||||
export const GET_TEAM_MEMBERS_USE_CASE_TOKEN = Symbol('GET_TEAM_MEMBERS_USE_CASE_TOKEN');
|
||||
export const GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN = Symbol('GET_TEAM_JOIN_REQUESTS_USE_CASE_TOKEN');
|
||||
export const CREATE_TEAM_USE_CASE_TOKEN = Symbol('CREATE_TEAM_USE_CASE_TOKEN');
|
||||
export const UPDATE_TEAM_USE_CASE_TOKEN = Symbol('UPDATE_TEAM_USE_CASE_TOKEN');
|
||||
export const GET_DRIVER_TEAM_USE_CASE_TOKEN = Symbol('GET_DRIVER_TEAM_USE_CASE_TOKEN');
|
||||
export const GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN = Symbol('GET_TEAM_MEMBERSHIP_USE_CASE_TOKEN');
|
||||
export const JOIN_TEAM_USE_CASE_TOKEN = Symbol('JOIN_TEAM_USE_CASE_TOKEN');
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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({
|
||||
|
||||
34
core/media/application/use-cases/GetUploadedMediaUseCase.ts
Normal file
34
core/media/application/use-cases/GetUploadedMediaUseCase.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { MediaStoragePort } from '../ports/MediaStoragePort';
|
||||
|
||||
export type GetUploadedMediaInput = {
|
||||
storageKey: string;
|
||||
};
|
||||
|
||||
export type GetUploadedMediaResult = {
|
||||
bytes: Buffer;
|
||||
contentType: string;
|
||||
};
|
||||
|
||||
export class GetUploadedMediaUseCase {
|
||||
constructor(private readonly mediaStorage: MediaStoragePort) {}
|
||||
|
||||
async execute(input: GetUploadedMediaInput): Promise<Result<GetUploadedMediaResult | null>> {
|
||||
try {
|
||||
const bytes = await this.mediaStorage.getBytes!(input.storageKey);
|
||||
if (!bytes) {
|
||||
return Result.ok(null);
|
||||
}
|
||||
|
||||
const metadata = await this.mediaStorage.getMetadata!(input.storageKey);
|
||||
const contentType = metadata?.contentType || 'application/octet-stream';
|
||||
|
||||
return Result.ok({
|
||||
bytes: Buffer.from(bytes),
|
||||
contentType,
|
||||
});
|
||||
} catch (error) {
|
||||
return Result.err(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { MediaReference } from '@core/domain/media/MediaReference';
|
||||
import type { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
|
||||
export type ResolveMediaReferenceInput = {
|
||||
reference: MediaReference;
|
||||
};
|
||||
|
||||
export class ResolveMediaReferenceUseCase {
|
||||
constructor(private readonly mediaResolver: MediaResolverPort) {}
|
||||
|
||||
async execute(input: ResolveMediaReferenceInput): Promise<Result<string | null>> {
|
||||
try {
|
||||
const resolvedPath = await this.mediaResolver.resolve(input.reference);
|
||||
return Result.ok(resolvedPath);
|
||||
} catch (error) {
|
||||
return Result.err(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Application Use Case: GetAllNotificationsUseCase
|
||||
*
|
||||
* Retrieves all notifications for a recipient.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@core/shared/application';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
import type { Notification } from '../../domain/entities/Notification';
|
||||
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
|
||||
|
||||
export type GetAllNotificationsInput = {
|
||||
recipientId: string;
|
||||
};
|
||||
|
||||
export interface GetAllNotificationsResult {
|
||||
notifications: Notification[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
export type GetAllNotificationsErrorCode = 'REPOSITORY_ERROR';
|
||||
|
||||
export class GetAllNotificationsUseCase {
|
||||
constructor(
|
||||
private readonly notificationRepository: INotificationRepository,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
input: GetAllNotificationsInput,
|
||||
): Promise<Result<GetAllNotificationsResult, ApplicationErrorCode<GetAllNotificationsErrorCode, { message: string }>>> {
|
||||
const { recipientId } = input;
|
||||
this.logger.debug(
|
||||
`Attempting to retrieve all notifications for recipient ID: ${recipientId}`,
|
||||
);
|
||||
|
||||
try {
|
||||
const notifications = await this.notificationRepository.findByRecipientId(
|
||||
recipientId,
|
||||
);
|
||||
this.logger.info(
|
||||
`Successfully retrieved ${notifications.length} notifications for recipient ID: ${recipientId}`,
|
||||
);
|
||||
|
||||
return Result.ok<GetAllNotificationsResult, ApplicationErrorCode<GetAllNotificationsErrorCode, { message: string }>>({
|
||||
notifications,
|
||||
totalCount: notifications.length,
|
||||
});
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
this.logger.error(
|
||||
`Failed to retrieve notifications for recipient ID: ${recipientId}`,
|
||||
err,
|
||||
);
|
||||
|
||||
return Result.err<GetAllNotificationsResult, ApplicationErrorCode<GetAllNotificationsErrorCode, { message: string }>>({
|
||||
code: 'REPOSITORY_ERROR',
|
||||
details: { message: err.message },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
20
core/racing/application/use-cases/GetDriverUseCase.ts
Normal file
20
core/racing/application/use-cases/GetDriverUseCase.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { Driver } from '../../domain/entities/Driver';
|
||||
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
|
||||
|
||||
export type GetDriverInput = {
|
||||
driverId: string;
|
||||
};
|
||||
|
||||
export class GetDriverUseCase {
|
||||
constructor(private readonly driverRepository: IDriverRepository) {}
|
||||
|
||||
async execute(input: GetDriverInput): Promise<Result<Driver | null>> {
|
||||
try {
|
||||
const driver = await this.driverRepository.findById(input.driverId);
|
||||
return Result.ok(driver);
|
||||
} catch (error) {
|
||||
return Result.err(error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user