Files
gridpilot.gg/apps/website/lib/services/payments/PaymentService.test.ts
2025-12-17 22:17:02 +01:00

506 lines
15 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { PaymentService } from './PaymentService';
import type { PaymentsApiClient } from '../../api/payments/PaymentsApiClient';
import type { PaymentListPresenter } from '../../presenters/PaymentListPresenter';
import type {
GetPaymentsOutputDto,
CreatePaymentInputDto,
CreatePaymentOutputDto,
GetMembershipFeesOutputDto,
GetPrizesOutputDto,
GetWalletOutputDto,
PaymentDto,
MembershipFeeDto,
PrizeDto,
WalletDto,
} from '../../dtos';
import type {
PaymentViewModel,
MembershipFeeViewModel,
PrizeViewModel,
WalletViewModel,
} from '../../view-models';
describe('PaymentService', () => {
let service: PaymentService;
let mockApiClient: PaymentsApiClient;
let mockPaymentListPresenter: PaymentListPresenter;
let mockPresentPayment: (dto: any) => PaymentViewModel;
let mockPresentMembershipFee: (dto: any) => MembershipFeeViewModel;
let mockPresentPrize: (dto: any) => PrizeViewModel;
let mockPresentWallet: (dto: any) => WalletViewModel;
beforeEach(() => {
mockApiClient = {
getPayments: vi.fn(),
createPayment: vi.fn(),
getMembershipFees: vi.fn(),
getPrizes: vi.fn(),
getWallet: vi.fn(),
} as unknown as PaymentsApiClient;
mockPaymentListPresenter = {
present: vi.fn(),
} as unknown as PaymentListPresenter;
mockPresentPayment = vi.fn();
mockPresentMembershipFee = vi.fn();
mockPresentPrize = vi.fn();
mockPresentWallet = vi.fn();
service = new PaymentService(
mockApiClient,
mockPaymentListPresenter,
mockPresentPayment,
mockPresentMembershipFee,
mockPresentPrize,
mockPresentWallet
);
});
describe('constructor', () => {
it('should create instance with injected dependencies', () => {
expect(service).toBeInstanceOf(PaymentService);
});
});
describe('getPayments', () => {
it('should fetch all payments and transform via presenter', async () => {
// Arrange
const mockDto: GetPaymentsOutputDto = {
payments: [
{
id: 'payment-1',
amount: 100,
currency: 'USD',
status: 'completed',
createdAt: '2024-01-01',
},
{
id: 'payment-2',
amount: 200,
currency: 'EUR',
status: 'pending',
createdAt: '2024-01-02',
},
],
};
const mockViewModels: PaymentViewModel[] = [
{
id: 'payment-1',
amount: 100,
currency: 'USD',
status: 'completed',
createdAt: '2024-01-01',
} as PaymentViewModel,
{
id: 'payment-2',
amount: 200,
currency: 'EUR',
status: 'pending',
createdAt: '2024-01-02',
} as PaymentViewModel,
];
vi.mocked(mockApiClient.getPayments).mockResolvedValue(mockDto);
vi.mocked(mockPaymentListPresenter.present).mockReturnValue(mockViewModels);
// Act
const result = await service.getPayments();
// Assert
expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined, undefined);
expect(mockPaymentListPresenter.present).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModels);
});
it('should filter payments by leagueId', async () => {
// Arrange
const leagueId = 'league-123';
const mockDto: GetPaymentsOutputDto = { payments: [] };
const mockViewModels: PaymentViewModel[] = [];
vi.mocked(mockApiClient.getPayments).mockResolvedValue(mockDto);
vi.mocked(mockPaymentListPresenter.present).mockReturnValue(mockViewModels);
// Act
await service.getPayments(leagueId);
// Assert
expect(mockApiClient.getPayments).toHaveBeenCalledWith(leagueId, undefined);
});
it('should filter payments by driverId', async () => {
// Arrange
const driverId = 'driver-456';
const mockDto: GetPaymentsOutputDto = { payments: [] };
const mockViewModels: PaymentViewModel[] = [];
vi.mocked(mockApiClient.getPayments).mockResolvedValue(mockDto);
vi.mocked(mockPaymentListPresenter.present).mockReturnValue(mockViewModels);
// Act
await service.getPayments(undefined, driverId);
// Assert
expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined, driverId);
});
it('should propagate errors from API client', async () => {
// Arrange
const error = new Error('Failed to fetch payments');
vi.mocked(mockApiClient.getPayments).mockRejectedValue(error);
// Act & Assert
await expect(service.getPayments()).rejects.toThrow('Failed to fetch payments');
expect(mockApiClient.getPayments).toHaveBeenCalled();
expect(mockPaymentListPresenter.present).not.toHaveBeenCalled();
});
});
describe('getPayment', () => {
it('should fetch single payment by ID', async () => {
// Arrange
const paymentId = 'payment-123';
const mockDto: GetPaymentsOutputDto = {
payments: [
{
id: paymentId,
amount: 100,
currency: 'USD',
status: 'completed',
createdAt: '2024-01-01',
},
],
};
const mockViewModel: PaymentViewModel = {
id: paymentId,
amount: 100,
currency: 'USD',
status: 'completed',
createdAt: '2024-01-01',
} as PaymentViewModel;
vi.mocked(mockApiClient.getPayments).mockResolvedValue(mockDto);
vi.mocked(mockPresentPayment).mockReturnValue(mockViewModel);
// Act
const result = await service.getPayment(paymentId);
// Assert
expect(mockApiClient.getPayments).toHaveBeenCalled();
expect(mockPresentPayment).toHaveBeenCalledWith(mockDto.payments[0]);
expect(result).toEqual(mockViewModel);
});
it('should throw error when payment not found', async () => {
// Arrange
const paymentId = 'non-existent';
const mockDto: GetPaymentsOutputDto = { payments: [] };
vi.mocked(mockApiClient.getPayments).mockResolvedValue(mockDto);
// Act & Assert
await expect(service.getPayment(paymentId)).rejects.toThrow(
`Payment with ID ${paymentId} not found`
);
});
it('should propagate errors from API client', async () => {
// Arrange
const error = new Error('API error');
vi.mocked(mockApiClient.getPayments).mockRejectedValue(error);
// Act & Assert
await expect(service.getPayment('payment-123')).rejects.toThrow('API error');
});
});
describe('createPayment', () => {
it('should create a new payment', async () => {
// Arrange
const input: CreatePaymentInputDto = {
amount: 150,
currency: 'USD',
leagueId: 'league-123',
driverId: 'driver-456',
};
const mockOutput: CreatePaymentOutputDto = {
paymentId: 'payment-new',
success: true,
};
vi.mocked(mockApiClient.createPayment).mockResolvedValue(mockOutput);
// Act
const result = await service.createPayment(input);
// Assert
expect(mockApiClient.createPayment).toHaveBeenCalledWith(input);
expect(result).toEqual(mockOutput);
});
it('should propagate errors from API client', async () => {
// Arrange
const input: CreatePaymentInputDto = {
amount: 150,
currency: 'USD',
};
const error = new Error('Payment creation failed');
vi.mocked(mockApiClient.createPayment).mockRejectedValue(error);
// Act & Assert
await expect(service.createPayment(input)).rejects.toThrow('Payment creation failed');
});
});
describe('getMembershipFees', () => {
it('should fetch membership fees for a league', async () => {
// Arrange
const leagueId = 'league-123';
const mockDto: GetMembershipFeesOutputDto = {
fees: [
{
leagueId,
amount: 50,
currency: 'USD',
period: 'monthly',
},
],
memberPayments: [],
};
const mockViewModel: MembershipFeeViewModel = {
leagueId,
amount: 50,
currency: 'USD',
period: 'monthly',
} as MembershipFeeViewModel;
vi.mocked(mockApiClient.getMembershipFees).mockResolvedValue(mockDto);
vi.mocked(mockPresentMembershipFee).mockReturnValue(mockViewModel);
// Act
const result = await service.getMembershipFees(leagueId);
// Assert
expect(mockApiClient.getMembershipFees).toHaveBeenCalledWith(leagueId);
expect(mockPresentMembershipFee).toHaveBeenCalledWith(mockDto.fees[0]);
expect(result).toEqual([mockViewModel]);
});
it('should handle empty fees list', async () => {
// Arrange
const leagueId = 'league-123';
const mockDto: GetMembershipFeesOutputDto = {
fees: [],
memberPayments: [],
};
vi.mocked(mockApiClient.getMembershipFees).mockResolvedValue(mockDto);
// Act
const result = await service.getMembershipFees(leagueId);
// Assert
expect(result).toEqual([]);
expect(mockPresentMembershipFee).not.toHaveBeenCalled();
});
it('should propagate errors from API client', async () => {
// Arrange
const error = new Error('Failed to fetch fees');
vi.mocked(mockApiClient.getMembershipFees).mockRejectedValue(error);
// Act & Assert
await expect(service.getMembershipFees('league-123')).rejects.toThrow('Failed to fetch fees');
});
});
describe('getPrizes', () => {
it('should fetch all prizes', async () => {
// Arrange
const mockDto: GetPrizesOutputDto = {
prizes: [
{
id: 'prize-1',
name: 'First Place',
amount: 1000,
currency: 'USD',
position: 1,
},
],
};
const mockViewModel: PrizeViewModel = {
id: 'prize-1',
name: 'First Place',
amount: 1000,
currency: 'USD',
position: 1,
} as PrizeViewModel;
vi.mocked(mockApiClient.getPrizes).mockResolvedValue(mockDto);
vi.mocked(mockPresentPrize).mockReturnValue(mockViewModel);
// Act
const result = await service.getPrizes();
// Assert
expect(mockApiClient.getPrizes).toHaveBeenCalledWith(undefined, undefined);
expect(mockPresentPrize).toHaveBeenCalledWith(mockDto.prizes[0]);
expect(result).toEqual([mockViewModel]);
});
it('should filter prizes by leagueId and seasonId', async () => {
// Arrange
const leagueId = 'league-123';
const seasonId = 'season-456';
const mockDto: GetPrizesOutputDto = { prizes: [] };
vi.mocked(mockApiClient.getPrizes).mockResolvedValue(mockDto);
// Act
await service.getPrizes(leagueId, seasonId);
// Assert
expect(mockApiClient.getPrizes).toHaveBeenCalledWith(leagueId, seasonId);
});
it('should propagate errors from API client', async () => {
// Arrange
const error = new Error('Failed to fetch prizes');
vi.mocked(mockApiClient.getPrizes).mockRejectedValue(error);
// Act & Assert
await expect(service.getPrizes()).rejects.toThrow('Failed to fetch prizes');
});
});
describe('getWallet', () => {
it('should fetch wallet for a driver', async () => {
// Arrange
const driverId = 'driver-123';
const mockDto: GetWalletOutputDto = {
driverId,
balance: 500,
currency: 'USD',
transactions: [],
};
const mockViewModel: WalletViewModel = {
driverId,
balance: 500,
currency: 'USD',
transactions: [],
} as WalletViewModel;
vi.mocked(mockApiClient.getWallet).mockResolvedValue(mockDto);
vi.mocked(mockPresentWallet).mockReturnValue(mockViewModel);
// Act
const result = await service.getWallet(driverId);
// Assert
expect(mockApiClient.getWallet).toHaveBeenCalledWith(driverId);
expect(mockPresentWallet).toHaveBeenCalledWith(mockDto);
expect(result).toEqual(mockViewModel);
});
it('should propagate errors from API client', async () => {
// Arrange
const error = new Error('Failed to fetch wallet');
vi.mocked(mockApiClient.getWallet).mockRejectedValue(error);
// Act & Assert
await expect(service.getWallet('driver-123')).rejects.toThrow('Failed to fetch wallet');
});
});
describe('processPayment', () => {
it('should process payment using createPayment', async () => {
// Arrange
const input: CreatePaymentInputDto = {
amount: 100,
currency: 'USD',
};
const mockOutput: CreatePaymentOutputDto = {
paymentId: 'payment-123',
success: true,
};
vi.mocked(mockApiClient.createPayment).mockResolvedValue(mockOutput);
// Act
const result = await service.processPayment(input);
// Assert
expect(mockApiClient.createPayment).toHaveBeenCalledWith(input);
expect(result).toEqual(mockOutput);
});
it('should propagate errors', async () => {
// Arrange
const input: CreatePaymentInputDto = {
amount: 100,
currency: 'USD',
};
const error = new Error('Processing failed');
vi.mocked(mockApiClient.createPayment).mockRejectedValue(error);
// Act & Assert
await expect(service.processPayment(input)).rejects.toThrow('Processing failed');
});
});
describe('getPaymentHistory', () => {
it('should fetch payment history for a driver', async () => {
// Arrange
const driverId = 'driver-123';
const mockDto: GetPaymentsOutputDto = {
payments: [
{
id: 'payment-1',
amount: 100,
currency: 'USD',
status: 'completed',
createdAt: '2024-01-01',
},
],
};
const mockViewModels: PaymentViewModel[] = [
{
id: 'payment-1',
amount: 100,
currency: 'USD',
status: 'completed',
createdAt: '2024-01-01',
} as PaymentViewModel,
];
vi.mocked(mockApiClient.getPayments).mockResolvedValue(mockDto);
vi.mocked(mockPaymentListPresenter.present).mockReturnValue(mockViewModels);
// Act
const result = await service.getPaymentHistory(driverId);
// Assert
expect(mockApiClient.getPayments).toHaveBeenCalledWith(undefined, driverId);
expect(result).toEqual(mockViewModels);
});
it('should propagate errors', async () => {
// Arrange
const error = new Error('History fetch failed');
vi.mocked(mockApiClient.getPayments).mockRejectedValue(error);
// Act & Assert
await expect(service.getPaymentHistory('driver-123')).rejects.toThrow('History fetch failed');
});
});
});