code quality
Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
Some checks failed
CI / lint-typecheck (pull_request) Failing after 12s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
This commit is contained in:
@@ -2216,6 +2216,96 @@
|
||||
"incidents"
|
||||
]
|
||||
},
|
||||
"DashboardStatsResponseDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"totalUsers": {
|
||||
"type": "number"
|
||||
},
|
||||
"activeUsers": {
|
||||
"type": "number"
|
||||
},
|
||||
"suspendedUsers": {
|
||||
"type": "number"
|
||||
},
|
||||
"deletedUsers": {
|
||||
"type": "number"
|
||||
},
|
||||
"systemAdmins": {
|
||||
"type": "number"
|
||||
},
|
||||
"recentLogins": {
|
||||
"type": "number"
|
||||
},
|
||||
"newUsersToday": {
|
||||
"type": "number"
|
||||
},
|
||||
"userGrowth": {
|
||||
"type": "object"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "number"
|
||||
},
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"roleDistribution": {
|
||||
"type": "object"
|
||||
},
|
||||
"statusDistribution": {
|
||||
"type": "object"
|
||||
},
|
||||
"active": {
|
||||
"type": "number"
|
||||
},
|
||||
"suspended": {
|
||||
"type": "number"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "number"
|
||||
},
|
||||
"activityTimeline": {
|
||||
"type": "object"
|
||||
},
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"newUsers": {
|
||||
"type": "number"
|
||||
},
|
||||
"logins": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"totalUsers",
|
||||
"activeUsers",
|
||||
"suspendedUsers",
|
||||
"deletedUsers",
|
||||
"systemAdmins",
|
||||
"recentLogins",
|
||||
"newUsersToday",
|
||||
"userGrowth",
|
||||
"label",
|
||||
"value",
|
||||
"color",
|
||||
"roleDistribution",
|
||||
"label",
|
||||
"value",
|
||||
"color",
|
||||
"statusDistribution",
|
||||
"active",
|
||||
"suspended",
|
||||
"deleted",
|
||||
"activityTimeline",
|
||||
"date",
|
||||
"newUsers",
|
||||
"logins"
|
||||
]
|
||||
},
|
||||
"DeleteMediaOutputDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { AdminController } from './AdminController';
|
||||
import { DashboardStatsResponseDto } from './dto/DashboardStatsResponseDto';
|
||||
import { DashboardStatsResponseDto } from './dtos/DashboardStatsResponseDto';
|
||||
import { ListUsersRequestDto } from './dtos/ListUsersRequestDto';
|
||||
import { UserListResponseDto, UserResponseDto } from './dtos/UserResponseDto';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RequireSystemAdmin, REQUIRE_SYSTEM_ADMIN_METADATA_KEY } from './Require
|
||||
|
||||
// Mock SetMetadata
|
||||
vi.mock('@nestjs/common', () => ({
|
||||
SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
SetMetadata: vi.fn(() => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
}));
|
||||
|
||||
describe('RequireSystemAdmin', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { AuthorizationService } from './AuthorizationService';
|
||||
|
||||
describe('AuthorizationService', () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Public, PUBLIC_ROUTE_METADATA_KEY } from './Public';
|
||||
|
||||
// Mock SetMetadata
|
||||
vi.mock('@nestjs/common', () => ({
|
||||
SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
SetMetadata: vi.fn(() => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
}));
|
||||
|
||||
describe('Public', () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RequireAuthenticatedUser, REQUIRE_AUTHENTICATED_USER_METADATA_KEY } fro
|
||||
|
||||
// Mock SetMetadata
|
||||
vi.mock('@nestjs/common', () => ({
|
||||
SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
SetMetadata: vi.fn(() => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
}));
|
||||
|
||||
describe('RequireAuthenticatedUser', () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RequireRoles, REQUIRE_ROLES_METADATA_KEY } from './RequireRoles';
|
||||
|
||||
// Mock SetMetadata
|
||||
vi.mock('@nestjs/common', () => ({
|
||||
SetMetadata: vi.fn(() => (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
SetMetadata: vi.fn(() => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) => descriptor),
|
||||
}));
|
||||
|
||||
describe('RequireRoles', () => {
|
||||
|
||||
@@ -49,6 +49,7 @@ const createOutput = (): DashboardOverviewResult => {
|
||||
fastestLap: 120,
|
||||
incidents: 0,
|
||||
startPosition: 1,
|
||||
points: 25,
|
||||
});
|
||||
|
||||
const feedItem: FeedItem = {
|
||||
|
||||
@@ -141,7 +141,7 @@ describe('requireLeagueAdminOrOwner', () => {
|
||||
try {
|
||||
await requireLeagueAdminOrOwner('league-123', mockGetLeagueAdminPermissionsUseCase);
|
||||
expect(true).toBe(false); // Should not reach here
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
expect(error).toBeInstanceOf(ForbiddenException);
|
||||
expect(error.message).toBe('Forbidden');
|
||||
}
|
||||
@@ -192,7 +192,7 @@ describe('requireLeagueAdminOrOwner', () => {
|
||||
mockGetActorFromRequestContext.mockReturnValue({
|
||||
userId: 'user-123',
|
||||
driverId: 'driver-123',
|
||||
role: null,
|
||||
role: undefined,
|
||||
});
|
||||
|
||||
const mockResult = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import { LeagueController } from './LeagueController';
|
||||
import { LeagueService } from './LeagueService';
|
||||
|
||||
@@ -25,9 +25,9 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
name: 'GT3 Masters',
|
||||
description: 'A GT3 racing league',
|
||||
ownerId: 'owner-1',
|
||||
maxDrivers: 32,
|
||||
currentDrivers: 25,
|
||||
isPublic: true,
|
||||
settings: { maxDrivers: 32 },
|
||||
usedSlots: 25,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
totalCount: 1,
|
||||
@@ -59,18 +59,18 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
name: 'Small League',
|
||||
description: 'Small league',
|
||||
ownerId: 'owner-1',
|
||||
maxDrivers: 10,
|
||||
currentDrivers: 8,
|
||||
isPublic: true,
|
||||
settings: { maxDrivers: 10 },
|
||||
usedSlots: 8,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'league-2',
|
||||
name: 'Large League',
|
||||
description: 'Large league',
|
||||
ownerId: 'owner-2',
|
||||
maxDrivers: 50,
|
||||
currentDrivers: 45,
|
||||
isPublic: true,
|
||||
settings: { maxDrivers: 50 },
|
||||
usedSlots: 45,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
totalCount: 2,
|
||||
@@ -81,8 +81,8 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(result.leagues).toHaveLength(2);
|
||||
expect(result.leagues[0]?.maxDrivers).toBe(10);
|
||||
expect(result.leagues[1]?.maxDrivers).toBe(50);
|
||||
expect(result.leagues[0]?.settings.maxDrivers).toBe(10);
|
||||
expect(result.leagues[1]?.settings.maxDrivers).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -95,13 +95,17 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
name: 'GT3 Masters',
|
||||
description: 'A GT3 racing league',
|
||||
ownerId: 'owner-1',
|
||||
maxDrivers: 32,
|
||||
currentDrivers: 25,
|
||||
isPublic: true,
|
||||
scoringConfig: {
|
||||
pointsSystem: 'standard',
|
||||
pointsPerRace: 25,
|
||||
bonusPoints: true,
|
||||
settings: { maxDrivers: 32 },
|
||||
usedSlots: 25,
|
||||
createdAt: new Date().toISOString(),
|
||||
scoring: {
|
||||
gameId: 'iracing',
|
||||
gameName: 'iRacing',
|
||||
primaryChampionshipType: 'driver',
|
||||
scoringPresetId: 'standard',
|
||||
scoringPresetName: 'Standard',
|
||||
dropPolicySummary: 'None',
|
||||
scoringPatternSummary: '25-18-15...',
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -134,13 +138,17 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
name: 'Standard League',
|
||||
description: 'Standard scoring',
|
||||
ownerId: 'owner-1',
|
||||
maxDrivers: 32,
|
||||
currentDrivers: 20,
|
||||
isPublic: true,
|
||||
scoringConfig: {
|
||||
pointsSystem: 'standard',
|
||||
pointsPerRace: 25,
|
||||
bonusPoints: true,
|
||||
settings: { maxDrivers: 32 },
|
||||
usedSlots: 20,
|
||||
createdAt: new Date().toISOString(),
|
||||
scoring: {
|
||||
gameId: 'iracing',
|
||||
gameName: 'iRacing',
|
||||
primaryChampionshipType: 'driver',
|
||||
scoringPresetId: 'standard',
|
||||
scoringPresetName: 'Standard',
|
||||
dropPolicySummary: 'None',
|
||||
scoringPatternSummary: '25-18-15...',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -148,13 +156,17 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
name: 'Custom League',
|
||||
description: 'Custom scoring',
|
||||
ownerId: 'owner-2',
|
||||
maxDrivers: 20,
|
||||
currentDrivers: 15,
|
||||
isPublic: true,
|
||||
scoringConfig: {
|
||||
pointsSystem: 'custom',
|
||||
pointsPerRace: 50,
|
||||
bonusPoints: false,
|
||||
settings: { maxDrivers: 20 },
|
||||
usedSlots: 15,
|
||||
createdAt: new Date().toISOString(),
|
||||
scoring: {
|
||||
gameId: 'iracing',
|
||||
gameName: 'iRacing',
|
||||
primaryChampionshipType: 'driver',
|
||||
scoringPresetId: 'custom',
|
||||
scoringPresetName: 'Custom',
|
||||
dropPolicySummary: 'None',
|
||||
scoringPatternSummary: '50-40-30...',
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -166,8 +178,8 @@ describe('LeagueController - Discovery Endpoints', () => {
|
||||
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(result.leagues).toHaveLength(2);
|
||||
expect(result.leagues[0]?.scoringConfig.pointsSystem).toBe('standard');
|
||||
expect(result.leagues[1]?.scoringConfig.pointsSystem).toBe('custom');
|
||||
expect(result.leagues[0]?.scoring?.scoringPresetId).toBe('standard');
|
||||
expect(result.leagues[1]?.scoring?.scoringPresetId).toBe('custom');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import { requestContextMiddleware } from '@adapters/http/RequestContext';
|
||||
import { Result } from '@core/shared/domain/Result';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { LeagueService } from './LeagueService';
|
||||
|
||||
async function withUserId<T>(userId: string, fn: () => Promise<T>): Promise<T> {
|
||||
const req = { user: { userId } };
|
||||
const res = {};
|
||||
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
requestContextMiddleware(req as never, res as never, () => {
|
||||
fn().then(resolve, reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('LeagueService - All Endpoints', () => {
|
||||
it('covers all league endpoint happy paths and error branches', async () => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { NotificationsController } from './NotificationsController';
|
||||
import { NotificationsService } from './NotificationsService';
|
||||
import { vi } from 'vitest';
|
||||
import type { Request, Response } from 'express';
|
||||
import { vi, describe, beforeEach, it, expect } from 'vitest';
|
||||
import type { Response } from 'express';
|
||||
|
||||
describe('NotificationsController', () => {
|
||||
let controller: NotificationsController;
|
||||
@@ -38,7 +38,7 @@ describe('NotificationsController', () => {
|
||||
|
||||
const mockReq = {
|
||||
user: { userId: 'user-123' },
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -53,7 +53,7 @@ describe('NotificationsController', () => {
|
||||
});
|
||||
|
||||
it('should return 401 when user is not authenticated', async () => {
|
||||
const mockReq = {} as unknown as Request;
|
||||
const mockReq = {} as any;
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
@@ -69,7 +69,7 @@ describe('NotificationsController', () => {
|
||||
it('should return 401 when userId is missing', async () => {
|
||||
const mockReq = {
|
||||
user: {},
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -90,7 +90,7 @@ describe('NotificationsController', () => {
|
||||
|
||||
const mockReq = {
|
||||
user: { userId: 'user-123' },
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -105,7 +105,7 @@ describe('NotificationsController', () => {
|
||||
});
|
||||
|
||||
it('should return 401 when user is not authenticated', async () => {
|
||||
const mockReq = {} as unknown as Request;
|
||||
const mockReq = {} as any;
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
@@ -121,7 +121,7 @@ describe('NotificationsController', () => {
|
||||
it('should return 401 when userId is missing', async () => {
|
||||
const mockReq = {
|
||||
user: {},
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -148,7 +148,7 @@ describe('NotificationsController', () => {
|
||||
|
||||
const mockReq = {
|
||||
user: { userId: 'user-123' },
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -163,7 +163,7 @@ describe('NotificationsController', () => {
|
||||
});
|
||||
|
||||
it('should return 401 when user is not authenticated', async () => {
|
||||
const mockReq = {} as unknown as Request;
|
||||
const mockReq = {} as any;
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
json: vi.fn(),
|
||||
@@ -179,7 +179,7 @@ describe('NotificationsController', () => {
|
||||
it('should return 401 when userId is missing', async () => {
|
||||
const mockReq = {
|
||||
user: {},
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
@@ -198,7 +198,7 @@ describe('NotificationsController', () => {
|
||||
|
||||
const mockReq = {
|
||||
user: { userId: 'user-123' },
|
||||
} as unknown as Request;
|
||||
} as any;
|
||||
|
||||
const mockRes = {
|
||||
status: vi.fn().mockReturnThis(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { CreatePaymentPresenter } from './CreatePaymentPresenter';
|
||||
import { CreatePaymentOutput } from '../dtos/PaymentsDto';
|
||||
import { PaymentType, PayerType, PaymentStatus } from '@core/payments/domain/entities/Payment';
|
||||
|
||||
describe('CreatePaymentPresenter', () => {
|
||||
let presenter: CreatePaymentPresenter;
|
||||
@@ -13,14 +14,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -31,14 +32,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
expect(responseModel).toEqual({
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
});
|
||||
@@ -48,15 +49,15 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -71,14 +72,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -94,14 +95,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -116,14 +117,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -144,14 +145,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -169,14 +170,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -191,14 +192,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const firstResult = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -206,14 +207,14 @@ describe('CreatePaymentPresenter', () => {
|
||||
const secondResult = {
|
||||
payment: {
|
||||
id: 'payment-456',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 200,
|
||||
platformFee: 10,
|
||||
netAmount: 190,
|
||||
payerId: 'user-456',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-456',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-02'),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GetMembershipFeesPresenter } from './GetMembershipFeesPresenter';
|
||||
import { GetMembershipFeesResultDTO } from '../dtos/GetMembershipFeesDTO';
|
||||
import { MembershipFeeType, MemberPaymentStatus } from '../dtos/PaymentsDto';
|
||||
import { MembershipFeeType } from '../dtos/PaymentsDto';
|
||||
|
||||
describe('GetMembershipFeesPresenter', () => {
|
||||
let presenter: GetMembershipFeesPresenter;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { GetPaymentsPresenter } from './GetPaymentsPresenter';
|
||||
import { GetPaymentsOutput } from '../dtos/PaymentsDto';
|
||||
import { PaymentType, PayerType, PaymentStatus } from '@core/payments/domain/entities/Payment';
|
||||
|
||||
describe('GetPaymentsPresenter', () => {
|
||||
let presenter: GetPaymentsPresenter;
|
||||
@@ -14,14 +15,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -34,14 +35,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -53,15 +54,15 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -70,7 +71,7 @@ describe('GetPaymentsPresenter', () => {
|
||||
presenter.present(result);
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel.payments[0].seasonId).toBe('season-123');
|
||||
expect(responseModel.payments[0]!.seasonId).toBe('season-123');
|
||||
});
|
||||
|
||||
it('should include completedAt when provided', () => {
|
||||
@@ -78,14 +79,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -95,7 +96,7 @@ describe('GetPaymentsPresenter', () => {
|
||||
presenter.present(result);
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel.payments[0].completedAt).toEqual(new Date('2024-01-02'));
|
||||
expect(responseModel.payments[0]!.completedAt).toEqual(new Date('2024-01-02'));
|
||||
});
|
||||
|
||||
it('should not include seasonId when not provided', () => {
|
||||
@@ -103,14 +104,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -119,7 +120,7 @@ describe('GetPaymentsPresenter', () => {
|
||||
presenter.present(result);
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel.payments[0].seasonId).toBeUndefined();
|
||||
expect(responseModel.payments[0]!.seasonId).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not include completedAt when not provided', () => {
|
||||
@@ -127,14 +128,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -143,7 +144,7 @@ describe('GetPaymentsPresenter', () => {
|
||||
presenter.present(result);
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel.payments[0].completedAt).toBeUndefined();
|
||||
expect(responseModel.payments[0]!.completedAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle empty payments list', () => {
|
||||
@@ -162,26 +163,26 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
{
|
||||
id: 'payment-456',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 200,
|
||||
platformFee: 10,
|
||||
netAmount: 190,
|
||||
payerId: 'user-456',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-456',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-02'),
|
||||
completedAt: new Date('2024-01-03'),
|
||||
},
|
||||
@@ -192,8 +193,8 @@ describe('GetPaymentsPresenter', () => {
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel.payments).toHaveLength(2);
|
||||
expect(responseModel.payments[0].id).toBe('payment-123');
|
||||
expect(responseModel.payments[1].id).toBe('payment-456');
|
||||
expect(responseModel.payments[0]!.id).toBe('payment-123');
|
||||
expect(responseModel.payments[1]!.id).toBe('payment-456');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -207,14 +208,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -224,7 +225,7 @@ describe('GetPaymentsPresenter', () => {
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel).toBeDefined();
|
||||
expect(responseModel.payments[0].id).toBe('payment-123');
|
||||
expect(responseModel.payments[0]!.id).toBe('payment-123');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -234,14 +235,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -258,14 +259,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-123',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
],
|
||||
@@ -275,14 +276,14 @@ describe('GetPaymentsPresenter', () => {
|
||||
payments: [
|
||||
{
|
||||
id: 'payment-456',
|
||||
type: 'membership',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 200,
|
||||
platformFee: 10,
|
||||
netAmount: 190,
|
||||
payerId: 'user-456',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-456',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-02'),
|
||||
},
|
||||
],
|
||||
@@ -293,7 +294,7 @@ describe('GetPaymentsPresenter', () => {
|
||||
presenter.present(secondResult);
|
||||
|
||||
const responseModel = presenter.getResponseModel();
|
||||
expect(responseModel.payments[0].id).toBe('payment-456');
|
||||
expect(responseModel.payments[0]!.id).toBe('payment-456');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -39,6 +43,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -52,6 +60,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.MERCHANDISE,
|
||||
amount: 200,
|
||||
leagueId: 'league-456',
|
||||
seasonId: 'season-456',
|
||||
position: 2,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -78,6 +90,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -99,6 +115,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -119,6 +139,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -132,6 +156,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.MERCHANDISE,
|
||||
amount: 200,
|
||||
leagueId: 'league-456',
|
||||
seasonId: 'season-456',
|
||||
position: 2,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -155,6 +183,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -178,6 +210,10 @@ describe('GetPrizesPresenter', () => {
|
||||
type: PrizeType.CASH,
|
||||
amount: 100,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it, beforeEach } from 'vitest';
|
||||
import { UpdatePaymentStatusPresenter } from './UpdatePaymentStatusPresenter';
|
||||
import { UpdatePaymentStatusOutput } from '../dtos/PaymentsDto';
|
||||
import { PaymentType, PayerType, PaymentStatus } from '@core/payments/domain/entities/Payment';
|
||||
|
||||
describe('UpdatePaymentStatusPresenter', () => {
|
||||
let presenter: UpdatePaymentStatusPresenter;
|
||||
@@ -13,14 +14,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -32,14 +33,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
expect(responseModel).toEqual({
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -50,15 +51,15 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
seasonId: 'season-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -74,14 +75,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -97,14 +98,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -119,14 +120,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
},
|
||||
};
|
||||
@@ -147,14 +148,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -173,14 +174,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const result = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -196,14 +197,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const firstResult = {
|
||||
payment: {
|
||||
id: 'payment-123',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'user-123',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-123',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
completedAt: new Date('2024-01-02'),
|
||||
},
|
||||
@@ -212,14 +213,14 @@ describe('UpdatePaymentStatusPresenter', () => {
|
||||
const secondResult = {
|
||||
payment: {
|
||||
id: 'payment-456',
|
||||
type: 'membership_fee',
|
||||
type: PaymentType.MEMBERSHIP_FEE,
|
||||
amount: 200,
|
||||
platformFee: 10,
|
||||
netAmount: 190,
|
||||
payerId: 'user-456',
|
||||
payerType: 'driver',
|
||||
payerType: PayerType.DRIVER,
|
||||
leagueId: 'league-456',
|
||||
status: 'completed',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-01-02'),
|
||||
completedAt: new Date('2024-01-03'),
|
||||
},
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
||||
import type { DataSource } from 'typeorm';
|
||||
|
||||
import { AdminUserOrmEntity } from '@core/admin/infrastructure/typeorm/entities/AdminUserOrmEntity';
|
||||
import { AdminUserOrmMapper } from '@core/admin/infrastructure/typeorm/mappers/AdminUserOrmMapper';
|
||||
import { TypeOrmAdminUserRepository } from '@core/admin/infrastructure/typeorm/repositories/TypeOrmAdminUserRepository';
|
||||
import { AdminUserOrmEntity } from '@adapters/admin/persistence/typeorm/entities/AdminUserOrmEntity';
|
||||
import { AdminUserOrmMapper } from '@adapters/admin/persistence/typeorm/mappers/AdminUserOrmMapper';
|
||||
import { TypeOrmAdminUserRepository } from '@adapters/admin/persistence/typeorm/repositories/TypeOrmAdminUserRepository';
|
||||
|
||||
import { ADMIN_USER_REPOSITORY_TOKEN } from '../admin/AdminPersistenceTokens';
|
||||
|
||||
|
||||
363
apps/api/src/shared/testing/contractValidation.test.ts
Normal file
363
apps/api/src/shared/testing/contractValidation.test.ts
Normal file
@@ -0,0 +1,363 @@
|
||||
/**
|
||||
* API Contract Validation Tests
|
||||
*
|
||||
* Validates that API DTOs are consistent and generate valid OpenAPI specs.
|
||||
* This test suite ensures contract compatibility between API and website.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
// Import DTO classes to validate their structure
|
||||
import { GetAnalyticsMetricsOutputDTO } from '../../domain/analytics/dtos/GetAnalyticsMetricsOutputDTO';
|
||||
import { GetDashboardDataOutputDTO } from '../../domain/analytics/dtos/GetDashboardDataOutputDTO';
|
||||
import { RecordEngagementInputDTO } from '../../domain/analytics/dtos/RecordEngagementInputDTO';
|
||||
import { RecordEngagementOutputDTO } from '../../domain/analytics/dtos/RecordEngagementOutputDTO';
|
||||
import { RecordPageViewInputDTO } from '../../domain/analytics/dtos/RecordPageViewInputDTO';
|
||||
import { RecordPageViewOutputDTO } from '../../domain/analytics/dtos/RecordPageViewOutputDTO';
|
||||
import { RequestAvatarGenerationInputDTO } from '../../domain/media/dtos/RequestAvatarGenerationInputDTO';
|
||||
import { RequestAvatarGenerationOutputDTO } from '../../domain/media/dtos/RequestAvatarGenerationOutputDTO';
|
||||
import { UploadMediaInputDTO } from '../../domain/media/dtos/UploadMediaInputDTO';
|
||||
import { UploadMediaOutputDTO } from '../../domain/media/dtos/UploadMediaOutputDTO';
|
||||
import { ValidateFaceInputDTO } from '../../domain/media/dtos/ValidateFaceInputDTO';
|
||||
import { ValidateFaceOutputDTO } from '../../domain/media/dtos/ValidateFaceOutputDTO';
|
||||
import { RaceDTO } from '../../domain/race/dtos/RaceDTO';
|
||||
import { RaceDetailDTO } from '../../domain/race/dtos/RaceDetailDTO';
|
||||
import { RaceResultDTO } from '../../domain/race/dtos/RaceResultDTO';
|
||||
import { SponsorDTO } from '../../domain/sponsor/dtos/SponsorDTO';
|
||||
import { SponsorshipDTO } from '../../domain/sponsor/dtos/SponsorshipDTO';
|
||||
import { TeamDTO } from '../../domain/team/dtos/TeamDto';
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
cyan: '\x1b[36m',
|
||||
dim: '\x1b[2m'
|
||||
};
|
||||
|
||||
describe('API Contract Validation', () => {
|
||||
let openApiSpec: any;
|
||||
let specPath: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Load the OpenAPI spec
|
||||
specPath = path.join(__dirname, '..', '..', '..', 'openapi.json');
|
||||
const specContent = await fs.readFile(specPath, 'utf-8');
|
||||
openApiSpec = JSON.parse(specContent);
|
||||
});
|
||||
|
||||
describe('OpenAPI Spec Integrity', () => {
|
||||
it('should have valid OpenAPI structure', () => {
|
||||
expect(openApiSpec).toBeDefined();
|
||||
expect(openApiSpec.openapi).toBeDefined();
|
||||
expect(openApiSpec.info).toBeDefined();
|
||||
expect(openApiSpec.paths).toBeDefined();
|
||||
expect(openApiSpec.components).toBeDefined();
|
||||
expect(openApiSpec.components.schemas).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have valid OpenAPI version', () => {
|
||||
expect(openApiSpec.openapi).toMatch(/^3\.\d+\.\d+$/);
|
||||
});
|
||||
|
||||
it('should have required API metadata', () => {
|
||||
expect(openApiSpec.info.title).toBeDefined();
|
||||
expect(openApiSpec.info.version).toBeDefined();
|
||||
expect(openApiSpec.info.description).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have no circular references in schemas', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
const visited = new Set<string>();
|
||||
const visiting = new Set<string>();
|
||||
|
||||
const checkCircular = (schemaName: string, schema: any): boolean => {
|
||||
if (!schema) return false;
|
||||
if (visiting.has(schemaName)) {
|
||||
return true; // Circular reference detected
|
||||
}
|
||||
if (visited.has(schemaName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
visiting.add(schemaName);
|
||||
|
||||
// Check $ref references
|
||||
if (schema.$ref) {
|
||||
const refName = schema.$ref.split('/').pop();
|
||||
if (schemas[refName] && checkCircular(refName, schemas[refName])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check properties
|
||||
if (schema.properties) {
|
||||
for (const prop of Object.values(schema.properties)) {
|
||||
if ((prop as any).$ref) {
|
||||
const refName = (prop as any).$ref.split('/').pop();
|
||||
if (schemas[refName] && checkCircular(refName, schemas[refName])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check array items
|
||||
if (schema.items && schema.items.$ref) {
|
||||
const refName = schema.items.$ref.split('/').pop();
|
||||
if (schemas[refName] && checkCircular(refName, schemas[refName])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
visiting.delete(schemaName);
|
||||
visited.add(schemaName);
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
if (checkCircular(schemaName, schema as any)) {
|
||||
throw new Error(`Circular reference detected in schema: ${schemaName}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should have all required DTOs in OpenAPI spec', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// List of critical DTOs that must exist in the spec
|
||||
const requiredDTOs = [
|
||||
'GetAnalyticsMetricsOutputDTO',
|
||||
'GetDashboardDataOutputDTO',
|
||||
'RecordEngagementInputDTO',
|
||||
'RecordEngagementOutputDTO',
|
||||
'RecordPageViewInputDTO',
|
||||
'RecordPageViewOutputDTO',
|
||||
'RequestAvatarGenerationInputDTO',
|
||||
'RequestAvatarGenerationOutputDTO',
|
||||
'UploadMediaInputDTO',
|
||||
'UploadMediaOutputDTO',
|
||||
'ValidateFaceInputDTO',
|
||||
'ValidateFaceOutputDTO',
|
||||
'RaceDTO',
|
||||
'RaceDetailDTO',
|
||||
'RaceResultDTO',
|
||||
'SponsorDTO',
|
||||
'SponsorshipDTO',
|
||||
'TeamDTO'
|
||||
];
|
||||
|
||||
for (const dtoName of requiredDTOs) {
|
||||
expect(schemas[dtoName], `DTO ${dtoName} should exist in OpenAPI spec`).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should have valid JSON schema for all DTOs', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
expect(schema, `Schema ${schemaName} should be an object`).toBeInstanceOf(Object);
|
||||
expect(schema.type, `Schema ${schemaName} should have a type`).toBeDefined();
|
||||
|
||||
if (schema.type === 'object') {
|
||||
expect(schema.properties, `Schema ${schemaName} should have properties`).toBeDefined();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('DTO Consistency', () => {
|
||||
it('should have consistent DTO definitions between code and spec', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// Test a sample of DTOs to ensure they match the spec
|
||||
const testDTOs = [
|
||||
{ name: 'GetAnalyticsMetricsOutputDTO', expectedProps: ['pageViews', 'uniqueVisitors', 'averageSessionDuration', 'bounceRate'] },
|
||||
{ name: 'RaceDTO', expectedProps: ['id', 'name', 'date'] },
|
||||
{ name: 'SponsorDTO', expectedProps: ['id', 'name'] }
|
||||
];
|
||||
|
||||
for (const { name, expectedProps } of testDTOs) {
|
||||
const schema = schemas[name];
|
||||
expect(schema, `Schema ${name} should exist`).toBeDefined();
|
||||
|
||||
if (schema.properties) {
|
||||
for (const prop of expectedProps) {
|
||||
expect(schema.properties[prop], `Property ${prop} should exist in ${name}`).toBeDefined();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should have no duplicate DTO names', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
const schemaNames = Object.keys(schemas);
|
||||
const uniqueNames = new Set(schemaNames);
|
||||
|
||||
expect(schemaNames.length).toBe(uniqueNames.size);
|
||||
});
|
||||
|
||||
it('should have consistent naming conventions', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
for (const schemaName of Object.keys(schemas)) {
|
||||
// DTO names should end with DTO
|
||||
expect(schemaName).toMatch(/DTO$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Type Generation Integrity', () => {
|
||||
it('should have all DTOs with proper type definitions', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
if (schema.type === 'object') {
|
||||
expect(schema.properties, `Schema ${schemaName} should have properties`).toBeDefined();
|
||||
|
||||
// Check that all properties have types or are references
|
||||
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
||||
const prop = propSchema as any;
|
||||
// Properties can have a type directly, or be a $ref to another schema
|
||||
const hasType = prop.type !== undefined;
|
||||
const isRef = prop.$ref !== undefined;
|
||||
|
||||
expect(hasType || isRef, `Property ${propName} in ${schemaName} should have a type or be a $ref`).toBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should have required fields properly marked', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// Test a few critical DTOs
|
||||
const testDTOs = [
|
||||
{ name: 'GetAnalyticsMetricsOutputDTO', required: ['pageViews', 'uniqueVisitors', 'averageSessionDuration', 'bounceRate'] },
|
||||
{ name: 'RaceDTO', required: ['id', 'name', 'date'] }
|
||||
];
|
||||
|
||||
for (const { name, required } of testDTOs) {
|
||||
const schema = schemas[name];
|
||||
expect(schema.required, `Schema ${name} should have required fields`).toBeDefined();
|
||||
|
||||
for (const field of required) {
|
||||
expect(schema.required).toContain(field);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should have nullable fields properly marked', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// Check that nullable fields are properly marked
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
if (schema.properties) {
|
||||
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
||||
if ((propSchema as any).nullable === true) {
|
||||
// Nullable fields should not be in required array
|
||||
if (schema.required) {
|
||||
expect(schema.required).not.toContain(propName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contract Compatibility', () => {
|
||||
it('should have backward compatible DTOs', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// Critical DTOs that must maintain backward compatibility
|
||||
const criticalDTOs = [
|
||||
'RaceDTO',
|
||||
'SponsorDTO',
|
||||
'TeamDTO',
|
||||
'DriverDTO'
|
||||
];
|
||||
|
||||
for (const dtoName of criticalDTOs) {
|
||||
const schema = schemas[dtoName];
|
||||
expect(schema, `Critical DTO ${dtoName} should exist`).toBeDefined();
|
||||
|
||||
// These DTOs should have required fields that cannot be removed
|
||||
if (schema.required) {
|
||||
expect(schema.required.length).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should have no breaking changes in required fields', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// Check that required fields are not empty for critical DTOs
|
||||
const criticalDTOs = ['RaceDTO', 'SponsorDTO', 'TeamDTO'];
|
||||
|
||||
for (const dtoName of criticalDTOs) {
|
||||
const schema = schemas[dtoName];
|
||||
if (schema && schema.required) {
|
||||
expect(schema.required.length).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should have consistent field types across versions', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
|
||||
// Check that common fields have consistent types
|
||||
const commonFields = {
|
||||
id: 'string',
|
||||
name: 'string',
|
||||
createdAt: 'string',
|
||||
updatedAt: 'string'
|
||||
};
|
||||
|
||||
for (const [fieldName, expectedType] of Object.entries(commonFields)) {
|
||||
for (const [schemaName, schema] of Object.entries(schemas)) {
|
||||
if (schema.properties && schema.properties[fieldName]) {
|
||||
expect(schema.properties[fieldName].type).toBe(expectedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Contract Validation Summary', () => {
|
||||
it('should pass all contract validation checks', () => {
|
||||
const schemas = openApiSpec.components.schemas as Record<string, any>;
|
||||
const schemaCount = Object.keys(schemas).length;
|
||||
|
||||
console.log(`${colors.cyan}📊 Contract Validation Summary${colors.reset}`);
|
||||
console.log(`${colors.dim} Total DTOs in OpenAPI spec: ${schemaCount}${colors.reset}`);
|
||||
console.log(`${colors.dim} Spec file: ${specPath}${colors.reset}`);
|
||||
|
||||
// Verify critical metrics
|
||||
expect(schemaCount).toBeGreaterThan(0);
|
||||
|
||||
// Count DTOs by category
|
||||
const analyticsDTOs = Object.keys(schemas).filter(name => name.includes('Analytics') || name.includes('Engagement') || name.includes('PageView'));
|
||||
const mediaDTOs = Object.keys(schemas).filter(name => name.includes('Media') || name.includes('Avatar'));
|
||||
const raceDTOs = Object.keys(schemas).filter(name => name.includes('Race'));
|
||||
const sponsorDTOs = Object.keys(schemas).filter(name => name.includes('Sponsor'));
|
||||
const teamDTOs = Object.keys(schemas).filter(name => name.includes('Team'));
|
||||
|
||||
console.log(`${colors.dim} Analytics DTOs: ${analyticsDTOs.length}${colors.reset}`);
|
||||
console.log(`${colors.dim} Media DTOs: ${mediaDTOs.length}${colors.reset}`);
|
||||
console.log(`${colors.dim} Race DTOs: ${raceDTOs.length}${colors.reset}`);
|
||||
console.log(`${colors.dim} Sponsor DTOs: ${sponsorDTOs.length}${colors.reset}`);
|
||||
console.log(`${colors.dim} Team DTOs: ${teamDTOs.length}${colors.reset}`);
|
||||
|
||||
// Verify that we have DTOs in each category
|
||||
expect(analyticsDTOs.length).toBeGreaterThan(0);
|
||||
expect(mediaDTOs.length).toBeGreaterThan(0);
|
||||
expect(raceDTOs.length).toBeGreaterThan(0);
|
||||
expect(sponsorDTOs.length).toBeGreaterThan(0);
|
||||
expect(teamDTOs.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user