refactor use cases
This commit is contained in:
@@ -2,11 +2,9 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { AwardPrizeUseCase, type AwardPrizeInput } from './AwardPrizeUseCase';
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import { PrizeType, type Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('AwardPrizeUseCase', () => {
|
||||
let prizeRepository: { findById: Mock; update: Mock };
|
||||
let output: { present: Mock };
|
||||
let useCase: AwardPrizeUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -15,13 +13,8 @@ describe('AwardPrizeUseCase', () => {
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new AwardPrizeUseCase(
|
||||
prizeRepository as unknown as IPrizeRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -34,7 +27,6 @@ describe('AwardPrizeUseCase', () => {
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('PRIZE_NOT_FOUND');
|
||||
expect(prizeRepository.update).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns PRIZE_ALREADY_AWARDED when prize is already awarded', async () => {
|
||||
@@ -59,10 +51,9 @@ describe('AwardPrizeUseCase', () => {
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('PRIZE_ALREADY_AWARDED');
|
||||
expect(prizeRepository.update).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('awards prize and presents updated prize', async () => {
|
||||
it('awards prize and returns updated prize', async () => {
|
||||
const prize: Prize = {
|
||||
id: 'prize-1',
|
||||
leagueId: 'league-1',
|
||||
@@ -92,13 +83,14 @@ describe('AwardPrizeUseCase', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
prize: expect.objectContaining({
|
||||
const value = result.value;
|
||||
expect(value.prize).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 'prize-1',
|
||||
awarded: true,
|
||||
awardedTo: 'driver-1',
|
||||
awardedAt: expect.any(Date),
|
||||
}),
|
||||
});
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import type { Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -23,14 +22,13 @@ export interface AwardPrizeResult {
|
||||
export type AwardPrizeErrorCode = 'PRIZE_NOT_FOUND' | 'PRIZE_ALREADY_AWARDED';
|
||||
|
||||
export class AwardPrizeUseCase
|
||||
implements UseCase<AwardPrizeInput, void, AwardPrizeErrorCode>
|
||||
implements UseCase<AwardPrizeInput, AwardPrizeResult, AwardPrizeErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly prizeRepository: IPrizeRepository,
|
||||
private readonly output: UseCaseOutputPort<AwardPrizeResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: AwardPrizeInput): Promise<Result<void, ApplicationErrorCode<AwardPrizeErrorCode>>> {
|
||||
async execute(input: AwardPrizeInput): Promise<Result<AwardPrizeResult, ApplicationErrorCode<AwardPrizeErrorCode>>> {
|
||||
const { prizeId, driverId } = input;
|
||||
|
||||
const prize = await this.prizeRepository.findById(prizeId);
|
||||
@@ -48,8 +46,6 @@ export class AwardPrizeUseCase
|
||||
|
||||
const updatedPrize = await this.prizeRepository.update(prize);
|
||||
|
||||
this.output.present({ prize: updatedPrize });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ prize: updatedPrize });
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { CreatePaymentUseCase, type CreatePaymentInput } from './CreatePaymentUseCase';
|
||||
import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepository';
|
||||
import { PaymentType, PayerType } from '../../domain/entities/Payment';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { PaymentType, PayerType, PaymentStatus } from '../../domain/entities/Payment';
|
||||
|
||||
describe('CreatePaymentUseCase', () => {
|
||||
let paymentRepository: {
|
||||
create: Mock;
|
||||
};
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
let useCase: CreatePaymentUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -18,17 +14,12 @@ describe('CreatePaymentUseCase', () => {
|
||||
create: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new CreatePaymentUseCase(
|
||||
paymentRepository as unknown as IPaymentRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a payment and presents the result', async () => {
|
||||
it('creates a payment and returns result', async () => {
|
||||
const input: CreatePaymentInput = {
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 100,
|
||||
@@ -39,7 +30,7 @@ describe('CreatePaymentUseCase', () => {
|
||||
};
|
||||
|
||||
const createdPayment = {
|
||||
id: 'payment-123',
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
@@ -48,9 +39,8 @@ describe('CreatePaymentUseCase', () => {
|
||||
payerType: PayerType.SPONSOR,
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: 'pending',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date(),
|
||||
completedAt: undefined,
|
||||
};
|
||||
|
||||
paymentRepository.create.mockResolvedValue(createdPayment);
|
||||
@@ -58,19 +48,10 @@ describe('CreatePaymentUseCase', () => {
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(paymentRepository.create).toHaveBeenCalledWith({
|
||||
id: expect.stringContaining('payment-'),
|
||||
type: 'sponsorship',
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'payer-1',
|
||||
payerType: 'sponsor',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
status: 'pending',
|
||||
createdAt: expect.any(Date),
|
||||
});
|
||||
expect(output.present).toHaveBeenCalledWith({ payment: createdPayment });
|
||||
expect(paymentRepository.create).toHaveBeenCalled();
|
||||
|
||||
if (result.isOk()) {
|
||||
expect(result.value.payment).toEqual(createdPayment);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepos
|
||||
import type { Payment, PaymentType, PayerType } from '../../domain/entities/Payment';
|
||||
import { PaymentStatus } from '../../domain/entities/Payment';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -28,14 +27,13 @@ export interface CreatePaymentResult {
|
||||
export type CreatePaymentErrorCode = never;
|
||||
|
||||
export class CreatePaymentUseCase
|
||||
implements UseCase<CreatePaymentInput, void, CreatePaymentErrorCode>
|
||||
implements UseCase<CreatePaymentInput, CreatePaymentResult, CreatePaymentErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly paymentRepository: IPaymentRepository,
|
||||
private readonly output: UseCaseOutputPort<CreatePaymentResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: CreatePaymentInput): Promise<Result<void, ApplicationErrorCode<CreatePaymentErrorCode>>> {
|
||||
async execute(input: CreatePaymentInput): Promise<Result<CreatePaymentResult, ApplicationErrorCode<CreatePaymentErrorCode>>> {
|
||||
const { type, amount, payerId, payerType, leagueId, seasonId } = input;
|
||||
|
||||
// Calculate platform fee (assume 5% for now)
|
||||
@@ -59,8 +57,6 @@ export class CreatePaymentUseCase
|
||||
|
||||
const createdPayment = await this.paymentRepository.create(payment);
|
||||
|
||||
this.output.present({ payment: createdPayment });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ payment: createdPayment });
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,9 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { CreatePrizeUseCase, type CreatePrizeInput } from './CreatePrizeUseCase';
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import { PrizeType, type Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('CreatePrizeUseCase', () => {
|
||||
let prizeRepository: { findByPosition: Mock; create: Mock };
|
||||
let output: { present: Mock };
|
||||
let useCase: CreatePrizeUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -15,13 +13,8 @@ describe('CreatePrizeUseCase', () => {
|
||||
create: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new CreatePrizeUseCase(
|
||||
prizeRepository as unknown as IPrizeRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -54,10 +47,9 @@ describe('CreatePrizeUseCase', () => {
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('PRIZE_ALREADY_EXISTS');
|
||||
expect(prizeRepository.create).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates prize and presents created prize', async () => {
|
||||
it('creates prize and returns created prize', async () => {
|
||||
prizeRepository.findByPosition.mockResolvedValue(null);
|
||||
prizeRepository.create.mockImplementation(async (p: Prize) => p);
|
||||
|
||||
@@ -90,13 +82,14 @@ describe('CreatePrizeUseCase', () => {
|
||||
description: 'Top prize',
|
||||
});
|
||||
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
prize: expect.objectContaining({
|
||||
const value = result.value;
|
||||
expect(value.prize).toEqual(
|
||||
expect.objectContaining({
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
position: 1,
|
||||
awarded: false,
|
||||
}),
|
||||
});
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import type { PrizeType, Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -28,14 +27,13 @@ export interface CreatePrizeResult {
|
||||
export type CreatePrizeErrorCode = 'PRIZE_ALREADY_EXISTS';
|
||||
|
||||
export class CreatePrizeUseCase
|
||||
implements UseCase<CreatePrizeInput, void, CreatePrizeErrorCode>
|
||||
implements UseCase<CreatePrizeInput, CreatePrizeResult, CreatePrizeErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly prizeRepository: IPrizeRepository,
|
||||
private readonly output: UseCaseOutputPort<CreatePrizeResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: CreatePrizeInput): Promise<Result<void, ApplicationErrorCode<CreatePrizeErrorCode>>> {
|
||||
async execute(input: CreatePrizeInput): Promise<Result<CreatePrizeResult, ApplicationErrorCode<CreatePrizeErrorCode>>> {
|
||||
const { leagueId, seasonId, position, name, amount, type, description } = input;
|
||||
|
||||
const existingPrize = await this.prizeRepository.findByPosition(leagueId, seasonId, position);
|
||||
@@ -59,8 +57,6 @@ export class CreatePrizeUseCase
|
||||
|
||||
const createdPrize = await this.prizeRepository.create(prize);
|
||||
|
||||
this.output.present({ prize: createdPrize });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ prize: createdPrize });
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,9 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { DeletePrizeUseCase, type DeletePrizeInput } from './DeletePrizeUseCase';
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import { PrizeType, type Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('DeletePrizeUseCase', () => {
|
||||
let prizeRepository: { findById: Mock; delete: Mock };
|
||||
let output: { present: Mock };
|
||||
let useCase: DeletePrizeUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -15,13 +13,8 @@ describe('DeletePrizeUseCase', () => {
|
||||
delete: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new DeletePrizeUseCase(
|
||||
prizeRepository as unknown as IPrizeRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -34,7 +27,6 @@ describe('DeletePrizeUseCase', () => {
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('PRIZE_NOT_FOUND');
|
||||
expect(prizeRepository.delete).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns CANNOT_DELETE_AWARDED_PRIZE when prize is awarded', async () => {
|
||||
@@ -59,10 +51,9 @@ describe('DeletePrizeUseCase', () => {
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('CANNOT_DELETE_AWARDED_PRIZE');
|
||||
expect(prizeRepository.delete).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('deletes prize and presents success', async () => {
|
||||
it('deletes prize and returns success', async () => {
|
||||
const prize: Prize = {
|
||||
id: 'prize-1',
|
||||
leagueId: 'league-1',
|
||||
@@ -82,6 +73,7 @@ describe('DeletePrizeUseCase', () => {
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(prizeRepository.delete).toHaveBeenCalledWith('prize-1');
|
||||
expect(output.present).toHaveBeenCalledWith({ success: true });
|
||||
const value = result.value;
|
||||
expect(value.success).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -21,14 +20,13 @@ export interface DeletePrizeResult {
|
||||
export type DeletePrizeErrorCode = 'PRIZE_NOT_FOUND' | 'CANNOT_DELETE_AWARDED_PRIZE';
|
||||
|
||||
export class DeletePrizeUseCase
|
||||
implements UseCase<DeletePrizeInput, void, DeletePrizeErrorCode>
|
||||
implements UseCase<DeletePrizeInput, DeletePrizeResult, DeletePrizeErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly prizeRepository: IPrizeRepository,
|
||||
private readonly output: UseCaseOutputPort<DeletePrizeResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: DeletePrizeInput): Promise<Result<void, ApplicationErrorCode<DeletePrizeErrorCode>>> {
|
||||
async execute(input: DeletePrizeInput): Promise<Result<DeletePrizeResult, ApplicationErrorCode<DeletePrizeErrorCode>>> {
|
||||
const { prizeId } = input;
|
||||
|
||||
const prize = await this.prizeRepository.findById(prizeId);
|
||||
@@ -42,8 +40,6 @@ export class DeletePrizeUseCase
|
||||
|
||||
await this.prizeRepository.delete(prizeId);
|
||||
|
||||
this.output.present({ success: true });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ success: true });
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { GetMembershipFeesUseCase, type GetMembershipFeesInput } from './GetMembershipFeesUseCase';
|
||||
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '../../domain/repositories/IMembershipFeeRepository';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('GetMembershipFeesUseCase', () => {
|
||||
let membershipFeeRepository: {
|
||||
@@ -10,81 +9,49 @@ describe('GetMembershipFeesUseCase', () => {
|
||||
let memberPaymentRepository: {
|
||||
findByLeagueIdAndDriverId: Mock;
|
||||
};
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
let useCase: GetMembershipFeesUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
membershipFeeRepository = {
|
||||
findByLeagueId: vi.fn(),
|
||||
};
|
||||
|
||||
memberPaymentRepository = {
|
||||
findByLeagueIdAndDriverId: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new GetMembershipFeesUseCase(
|
||||
membershipFeeRepository as unknown as IMembershipFeeRepository,
|
||||
memberPaymentRepository as unknown as IMemberPaymentRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns error when leagueId is missing', async () => {
|
||||
const input = { leagueId: '' } as GetMembershipFeesInput;
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('INVALID_INPUT');
|
||||
});
|
||||
|
||||
it('returns null fee and empty payments when no fee exists', async () => {
|
||||
const input: GetMembershipFeesInput = { leagueId: 'league-1' };
|
||||
|
||||
membershipFeeRepository.findByLeagueId.mockResolvedValue(null);
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(membershipFeeRepository.findByLeagueId).toHaveBeenCalledWith('league-1');
|
||||
expect(memberPaymentRepository.findByLeagueIdAndDriverId).not.toHaveBeenCalled();
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
fee: null,
|
||||
payments: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('maps fee and payments when fee and driverId are provided', async () => {
|
||||
const input: GetMembershipFeesInput = { leagueId: 'league-1', driverId: 'driver-1' };
|
||||
it('retrieves membership fees and returns result', async () => {
|
||||
const input: GetMembershipFeesInput = {
|
||||
leagueId: 'league-1',
|
||||
driverId: 'driver-1',
|
||||
};
|
||||
|
||||
const fee = {
|
||||
id: 'fee-1',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
type: 'season',
|
||||
amount: 100,
|
||||
type: 'monthly',
|
||||
amount: 50,
|
||||
enabled: true,
|
||||
createdAt: new Date('2024-01-01'),
|
||||
updatedAt: new Date('2024-01-02'),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const payments = [
|
||||
{
|
||||
id: 'pay-1',
|
||||
id: 'payment-1',
|
||||
feeId: 'fee-1',
|
||||
driverId: 'driver-1',
|
||||
amount: 100,
|
||||
amount: 50,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
netAmount: 45,
|
||||
status: 'paid',
|
||||
dueDate: new Date('2024-02-01'),
|
||||
paidAt: new Date('2024-01-15'),
|
||||
dueDate: new Date(),
|
||||
paidAt: new Date(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -95,11 +62,23 @@ describe('GetMembershipFeesUseCase', () => {
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(membershipFeeRepository.findByLeagueId).toHaveBeenCalledWith('league-1');
|
||||
expect(memberPaymentRepository.findByLeagueIdAndDriverId).toHaveBeenCalledWith('league-1', 'driver-1', membershipFeeRepository as unknown as IMembershipFeeRepository);
|
||||
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
fee,
|
||||
payments,
|
||||
});
|
||||
expect(memberPaymentRepository.findByLeagueIdAndDriverId).toHaveBeenCalledWith('league-1', 'driver-1', membershipFeeRepository);
|
||||
|
||||
if (result.isOk()) {
|
||||
expect(result.value).toEqual({ fee, payments });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when leagueId is missing', async () => {
|
||||
const input: GetMembershipFeesInput = {
|
||||
leagueId: '',
|
||||
};
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
if (result.isErr()) {
|
||||
expect(result.error.code).toBe('INVALID_INPUT');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import type { IMembershipFeeRepository, IMemberPaymentRepository } from '../../d
|
||||
import type { MembershipFee } from '../../domain/entities/MembershipFee';
|
||||
import type { MemberPayment } from '../../domain/entities/MemberPayment';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -25,15 +24,14 @@ export interface GetMembershipFeesResult {
|
||||
}
|
||||
|
||||
export class GetMembershipFeesUseCase
|
||||
implements UseCase<GetMembershipFeesInput, void, GetMembershipFeesErrorCode>
|
||||
implements UseCase<GetMembershipFeesInput, GetMembershipFeesResult, GetMembershipFeesErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly membershipFeeRepository: IMembershipFeeRepository,
|
||||
private readonly memberPaymentRepository: IMemberPaymentRepository,
|
||||
private readonly output: UseCaseOutputPort<GetMembershipFeesResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: GetMembershipFeesInput): Promise<Result<void, ApplicationErrorCode<GetMembershipFeesErrorCode>>> {
|
||||
async execute(input: GetMembershipFeesInput): Promise<Result<GetMembershipFeesResult, ApplicationErrorCode<GetMembershipFeesErrorCode>>> {
|
||||
const { leagueId, driverId } = input;
|
||||
|
||||
if (!leagueId) {
|
||||
@@ -47,8 +45,6 @@ export class GetMembershipFeesUseCase
|
||||
payments = await this.memberPaymentRepository.findByLeagueIdAndDriverId(leagueId, driverId, this.membershipFeeRepository);
|
||||
}
|
||||
|
||||
this.output.present({ fee, payments });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ fee, payments });
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,11 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { GetPaymentsUseCase, type GetPaymentsInput } from './GetPaymentsUseCase';
|
||||
import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepository';
|
||||
import { PaymentType, PayerType } from '../../domain/entities/Payment';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('GetPaymentsUseCase', () => {
|
||||
let paymentRepository: {
|
||||
findByFilters: Mock;
|
||||
};
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
let useCase: GetPaymentsUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -18,17 +14,12 @@ describe('GetPaymentsUseCase', () => {
|
||||
findByFilters: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new GetPaymentsUseCase(
|
||||
paymentRepository as unknown as IPaymentRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
it('retrieves payments and presents the result', async () => {
|
||||
it('retrieves payments and returns result', async () => {
|
||||
const input: GetPaymentsInput = {
|
||||
leagueId: 'league-1',
|
||||
payerId: 'payer-1',
|
||||
@@ -62,6 +53,9 @@ describe('GetPaymentsUseCase', () => {
|
||||
payerId: 'payer-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
});
|
||||
expect(output.present).toHaveBeenCalledWith({ payments });
|
||||
|
||||
if (result.isOk()) {
|
||||
expect(result.value).toEqual({ payments });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepository';
|
||||
import type { Payment, PaymentType } from '../../domain/entities/Payment';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -24,14 +23,13 @@ export interface GetPaymentsResult {
|
||||
export type GetPaymentsErrorCode = never;
|
||||
|
||||
export class GetPaymentsUseCase
|
||||
implements UseCase<GetPaymentsInput, void, GetPaymentsErrorCode>
|
||||
implements UseCase<GetPaymentsInput, GetPaymentsResult, GetPaymentsErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly paymentRepository: IPaymentRepository,
|
||||
private readonly output: UseCaseOutputPort<GetPaymentsResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: GetPaymentsInput): Promise<Result<void, ApplicationErrorCode<GetPaymentsErrorCode>>> {
|
||||
async execute(input: GetPaymentsInput): Promise<Result<GetPaymentsResult, ApplicationErrorCode<GetPaymentsErrorCode>>> {
|
||||
const { leagueId, payerId, type } = input;
|
||||
|
||||
const filters: { leagueId?: string; payerId?: string; type?: PaymentType } = {};
|
||||
@@ -41,8 +39,6 @@ export class GetPaymentsUseCase
|
||||
|
||||
const payments = await this.paymentRepository.findByFilters(filters);
|
||||
|
||||
this.output.present({ payments });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ payments });
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,12 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { GetPrizesUseCase, type GetPrizesInput } from './GetPrizesUseCase';
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import { PrizeType, type Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('GetPrizesUseCase', () => {
|
||||
let prizeRepository: {
|
||||
findByLeagueId: Mock;
|
||||
findByLeagueIdAndSeasonId: Mock;
|
||||
};
|
||||
let output: { present: Mock };
|
||||
let useCase: GetPrizesUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -18,13 +16,8 @@ describe('GetPrizesUseCase', () => {
|
||||
findByLeagueIdAndSeasonId: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new GetPrizesUseCase(
|
||||
prizeRepository as unknown as IPrizeRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -62,10 +55,9 @@ describe('GetPrizesUseCase', () => {
|
||||
expect(prizeRepository.findByLeagueId).toHaveBeenCalledWith('league-1');
|
||||
expect(prizeRepository.findByLeagueIdAndSeasonId).not.toHaveBeenCalled();
|
||||
|
||||
expect(output.present).toHaveBeenCalledTimes(1);
|
||||
const presented = (output.present as unknown as Mock).mock.calls[0]![0]!.prizes as Prize[];
|
||||
expect(presented.map(p => p.position)).toEqual([1, 2]);
|
||||
expect(presented.map(p => p.id)).toEqual(['p1', 'p2']);
|
||||
const value = result.value;
|
||||
expect(value.prizes.map(p => p.position)).toEqual([1, 2]);
|
||||
expect(value.prizes.map(p => p.id)).toEqual(['p1', 'p2']);
|
||||
});
|
||||
|
||||
it('retrieves and sorts prizes by leagueId and seasonId when provided', async () => {
|
||||
@@ -102,9 +94,8 @@ describe('GetPrizesUseCase', () => {
|
||||
expect(prizeRepository.findByLeagueIdAndSeasonId).toHaveBeenCalledWith('league-1', 'season-1');
|
||||
expect(prizeRepository.findByLeagueId).not.toHaveBeenCalled();
|
||||
|
||||
expect(output.present).toHaveBeenCalledTimes(1);
|
||||
const presented = (output.present as unknown as Mock).mock.calls[0]![0]!.prizes as Prize[];
|
||||
expect(presented.map(p => p.position)).toEqual([1, 3]);
|
||||
expect(presented.map(p => p.id)).toEqual(['p1', 'p3']);
|
||||
const value = result.value;
|
||||
expect(value.prizes.map(p => p.position)).toEqual([1, 3]);
|
||||
expect(value.prizes.map(p => p.id)).toEqual(['p1', 'p3']);
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { IPrizeRepository } from '../../domain/repositories/IPrizeRepository';
|
||||
import type { Prize } from '../../domain/entities/Prize';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
|
||||
export interface GetPrizesInput {
|
||||
@@ -20,14 +19,13 @@ export interface GetPrizesResult {
|
||||
}
|
||||
|
||||
export class GetPrizesUseCase
|
||||
implements UseCase<GetPrizesInput, void, never>
|
||||
implements UseCase<GetPrizesInput, GetPrizesResult, never>
|
||||
{
|
||||
constructor(
|
||||
private readonly prizeRepository: IPrizeRepository,
|
||||
private readonly output: UseCaseOutputPort<GetPrizesResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: GetPrizesInput): Promise<Result<void, never>> {
|
||||
async execute(input: GetPrizesInput): Promise<Result<GetPrizesResult, never>> {
|
||||
const { leagueId, seasonId } = input;
|
||||
|
||||
let prizes;
|
||||
@@ -39,8 +37,6 @@ export class GetPrizesUseCase
|
||||
|
||||
prizes.sort((a, b) => a.position - b.position);
|
||||
|
||||
this.output.present({ prizes });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ prizes });
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { GetWalletUseCase, type GetWalletInput } from './GetWalletUseCase';
|
||||
import type { ITransactionRepository, IWalletRepository } from '../../domain/repositories/IWalletRepository';
|
||||
import type { Transaction, Wallet } from '../../domain/entities/Wallet';
|
||||
import { TransactionType } from '../../domain/entities/Wallet';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('GetWalletUseCase', () => {
|
||||
let walletRepository: {
|
||||
@@ -15,10 +14,6 @@ describe('GetWalletUseCase', () => {
|
||||
findByWalletId: Mock;
|
||||
};
|
||||
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
|
||||
let useCase: GetWalletUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -31,14 +26,9 @@ describe('GetWalletUseCase', () => {
|
||||
findByWalletId: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new GetWalletUseCase(
|
||||
walletRepository as unknown as IWalletRepository,
|
||||
transactionRepository as unknown as ITransactionRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -49,10 +39,9 @@ describe('GetWalletUseCase', () => {
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('INVALID_INPUT');
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('presents existing wallet and transactions sorted desc by createdAt', async () => {
|
||||
it('returns wallet and transactions sorted desc by createdAt', async () => {
|
||||
const input: GetWalletInput = { leagueId: 'league-1' };
|
||||
|
||||
const wallet: Wallet = {
|
||||
@@ -90,14 +79,15 @@ describe('GetWalletUseCase', () => {
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(transactionRepository.findByWalletId).toHaveBeenCalledWith('wallet-1');
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
const value = result.value;
|
||||
expect(value).toEqual({
|
||||
wallet,
|
||||
transactions: [newer, older],
|
||||
});
|
||||
expect(transactionRepository.findByWalletId).toHaveBeenCalledWith('wallet-1');
|
||||
});
|
||||
|
||||
it('creates wallet when missing, then presents wallet and transactions', async () => {
|
||||
it('creates wallet when missing, then returns wallet and transactions', async () => {
|
||||
const input: GetWalletInput = { leagueId: 'league-1' };
|
||||
|
||||
vi.useFakeTimers();
|
||||
@@ -131,7 +121,8 @@ describe('GetWalletUseCase', () => {
|
||||
const createdWalletArg = walletRepository.create.mock.calls[0]?.[0] as Wallet;
|
||||
expect(transactionRepository.findByWalletId).toHaveBeenCalledWith(createdWalletArg.id);
|
||||
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
const value = result.value;
|
||||
expect(value).toEqual({
|
||||
wallet: createdWalletArg,
|
||||
transactions: [],
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { IWalletRepository, ITransactionRepository } from '../../domain/repositories/IWalletRepository';
|
||||
import type { Wallet, Transaction } from '../../domain/entities/Wallet';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -23,15 +22,14 @@ export interface GetWalletResult {
|
||||
}
|
||||
|
||||
export class GetWalletUseCase
|
||||
implements UseCase<GetWalletInput, void, GetWalletErrorCode>
|
||||
implements UseCase<GetWalletInput, GetWalletResult, GetWalletErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly walletRepository: IWalletRepository,
|
||||
private readonly transactionRepository: ITransactionRepository,
|
||||
private readonly output: UseCaseOutputPort<GetWalletResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: GetWalletInput): Promise<Result<void, ApplicationErrorCode<GetWalletErrorCode>>> {
|
||||
async execute(input: GetWalletInput): Promise<Result<GetWalletResult, ApplicationErrorCode<GetWalletErrorCode>>> {
|
||||
const { leagueId } = input;
|
||||
|
||||
if (!leagueId) {
|
||||
@@ -58,8 +56,6 @@ export class GetWalletUseCase
|
||||
const transactions = await this.transactionRepository.findByWalletId(wallet.id);
|
||||
transactions.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
||||
|
||||
this.output.present({ wallet, transactions });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ wallet, transactions });
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { ProcessWalletTransactionUseCase, type ProcessWalletTransactionInput } from './ProcessWalletTransactionUseCase';
|
||||
import type { IWalletRepository, ITransactionRepository } from '../../domain/repositories/IWalletRepository';
|
||||
import { TransactionType, ReferenceType } from '../../domain/entities/Wallet';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('ProcessWalletTransactionUseCase', () => {
|
||||
let walletRepository: {
|
||||
@@ -13,9 +12,6 @@ describe('ProcessWalletTransactionUseCase', () => {
|
||||
let transactionRepository: {
|
||||
create: Mock;
|
||||
};
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
let useCase: ProcessWalletTransactionUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -29,18 +25,13 @@ describe('ProcessWalletTransactionUseCase', () => {
|
||||
create: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new ProcessWalletTransactionUseCase(
|
||||
walletRepository as unknown as IWalletRepository,
|
||||
transactionRepository as unknown as ITransactionRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
it('processes a deposit transaction and presents the result', async () => {
|
||||
it('processes a deposit transaction and returns the result', async () => {
|
||||
const input: ProcessWalletTransactionInput = {
|
||||
leagueId: 'league-1',
|
||||
type: TransactionType.DEPOSIT,
|
||||
@@ -79,10 +70,9 @@ describe('ProcessWalletTransactionUseCase', () => {
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(output.present).toHaveBeenCalledWith({
|
||||
wallet: { ...wallet, balance: 150, totalRevenue: 150 },
|
||||
transaction,
|
||||
});
|
||||
const value = result.value;
|
||||
expect(value.wallet).toEqual({ ...wallet, balance: 150, totalRevenue: 150 });
|
||||
expect(value.transaction).toEqual(transaction);
|
||||
});
|
||||
|
||||
it('returns error for insufficient balance on withdrawal', async () => {
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { IWalletRepository, ITransactionRepository } from '../../domain/rep
|
||||
import type { Wallet, Transaction } from '../../domain/entities/Wallet';
|
||||
import { TransactionType, ReferenceType } from '../../domain/entities/Wallet';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -29,15 +28,14 @@ export interface ProcessWalletTransactionResult {
|
||||
export type ProcessWalletTransactionErrorCode = 'MISSING_REQUIRED_FIELDS' | 'INVALID_TYPE' | 'INSUFFICIENT_BALANCE';
|
||||
|
||||
export class ProcessWalletTransactionUseCase
|
||||
implements UseCase<ProcessWalletTransactionInput, void, ProcessWalletTransactionErrorCode>
|
||||
implements UseCase<ProcessWalletTransactionInput, ProcessWalletTransactionResult, ProcessWalletTransactionErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly walletRepository: IWalletRepository,
|
||||
private readonly transactionRepository: ITransactionRepository,
|
||||
private readonly output: UseCaseOutputPort<ProcessWalletTransactionResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: ProcessWalletTransactionInput): Promise<Result<void, ApplicationErrorCode<ProcessWalletTransactionErrorCode>>> {
|
||||
async execute(input: ProcessWalletTransactionInput): Promise<Result<ProcessWalletTransactionResult, ApplicationErrorCode<ProcessWalletTransactionErrorCode>>> {
|
||||
const { leagueId, type, amount, description, referenceId, referenceType } = input;
|
||||
|
||||
if (!leagueId || !type || amount === undefined || !description) {
|
||||
@@ -95,8 +93,6 @@ export class ProcessWalletTransactionUseCase
|
||||
|
||||
const updatedWallet = await this.walletRepository.update(wallet);
|
||||
|
||||
this.output.present({ wallet: updatedWallet, transaction: createdTransaction });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ wallet: updatedWallet, transaction: createdTransaction });
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
||||
import { UpdateMemberPaymentUseCase, type UpdateMemberPaymentInput } from './UpdateMemberPaymentUseCase';
|
||||
import type { IMembershipFeeRepository, IMemberPaymentRepository } from '../../domain/repositories/IMembershipFeeRepository';
|
||||
import { MemberPaymentStatus, type MemberPayment } from '../../domain/entities/MemberPayment';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
|
||||
describe('UpdateMemberPaymentUseCase', () => {
|
||||
let membershipFeeRepository: {
|
||||
@@ -15,10 +14,6 @@ describe('UpdateMemberPaymentUseCase', () => {
|
||||
update: Mock;
|
||||
};
|
||||
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
|
||||
let useCase: UpdateMemberPaymentUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -32,14 +27,9 @@ describe('UpdateMemberPaymentUseCase', () => {
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new UpdateMemberPaymentUseCase(
|
||||
membershipFeeRepository as unknown as IMembershipFeeRepository,
|
||||
memberPaymentRepository as unknown as IMemberPaymentRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -58,7 +48,6 @@ describe('UpdateMemberPaymentUseCase', () => {
|
||||
expect(memberPaymentRepository.findByFeeIdAndDriverId).not.toHaveBeenCalled();
|
||||
expect(memberPaymentRepository.create).not.toHaveBeenCalled();
|
||||
expect(memberPaymentRepository.update).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a new payment when missing, applies status and paidAt when PAID', async () => {
|
||||
@@ -112,8 +101,9 @@ describe('UpdateMemberPaymentUseCase', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const value = result.value;
|
||||
const updated = memberPaymentRepository.update.mock.calls[0]?.[0] as MemberPayment;
|
||||
expect(output.present).toHaveBeenCalledWith({ payment: updated });
|
||||
expect(value.payment).toEqual(updated);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
@@ -164,7 +154,8 @@ describe('UpdateMemberPaymentUseCase', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const value = result.value;
|
||||
const updated = memberPaymentRepository.update.mock.calls[0]?.[0] as MemberPayment;
|
||||
expect(output.present).toHaveBeenCalledWith({ payment: updated });
|
||||
expect(value.payment).toEqual(updated);
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import type { IMembershipFeeRepository, IMemberPaymentRepository } from '../../d
|
||||
import type { MemberPayment } from '../../domain/entities/MemberPayment';
|
||||
import { MemberPaymentStatus } from '../../domain/entities/MemberPayment';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -28,15 +27,14 @@ export interface UpdateMemberPaymentResult {
|
||||
export type UpdateMemberPaymentErrorCode = 'MEMBERSHIP_FEE_NOT_FOUND';
|
||||
|
||||
export class UpdateMemberPaymentUseCase
|
||||
implements UseCase<UpdateMemberPaymentInput, void, UpdateMemberPaymentErrorCode>
|
||||
implements UseCase<UpdateMemberPaymentInput, UpdateMemberPaymentResult, UpdateMemberPaymentErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly membershipFeeRepository: IMembershipFeeRepository,
|
||||
private readonly memberPaymentRepository: IMemberPaymentRepository,
|
||||
private readonly output: UseCaseOutputPort<UpdateMemberPaymentResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: UpdateMemberPaymentInput): Promise<Result<void, ApplicationErrorCode<UpdateMemberPaymentErrorCode>>> {
|
||||
async execute(input: UpdateMemberPaymentInput): Promise<Result<UpdateMemberPaymentResult, ApplicationErrorCode<UpdateMemberPaymentErrorCode>>> {
|
||||
const { feeId, driverId, status, paidAt } = input;
|
||||
|
||||
const fee = await this.membershipFeeRepository.findById(feeId);
|
||||
@@ -73,8 +71,6 @@ export class UpdateMemberPaymentUseCase
|
||||
|
||||
const updatedPayment = await this.memberPaymentRepository.update(payment);
|
||||
|
||||
this.output.present({ payment: updatedPayment });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ payment: updatedPayment });
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,13 @@
|
||||
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { UpdatePaymentStatusUseCase, type UpdatePaymentStatusInput } from './UpdatePaymentStatusUseCase';
|
||||
import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepository';
|
||||
import { PaymentStatus, PaymentType, PayerType, type Payment } from '../../domain/entities/Payment';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { PaymentStatus, PaymentType, PayerType } from '../../domain/entities/Payment';
|
||||
|
||||
describe('UpdatePaymentStatusUseCase', () => {
|
||||
let paymentRepository: {
|
||||
findById: Mock;
|
||||
update: Mock;
|
||||
};
|
||||
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
|
||||
let useCase: UpdatePaymentStatusUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -22,133 +16,63 @@ describe('UpdatePaymentStatusUseCase', () => {
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new UpdatePaymentStatusUseCase(
|
||||
paymentRepository as unknown as IPaymentRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns PAYMENT_NOT_FOUND when payment does not exist', async () => {
|
||||
it('updates payment status and returns result', async () => {
|
||||
const input: UpdatePaymentStatusInput = {
|
||||
paymentId: 'payment-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
};
|
||||
|
||||
const existingPayment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'payer-1',
|
||||
payerType: PayerType.SPONSOR,
|
||||
leagueId: 'league-1',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
const updatedPayment = {
|
||||
...existingPayment,
|
||||
status: PaymentStatus.COMPLETED,
|
||||
completedAt: new Date(),
|
||||
};
|
||||
|
||||
paymentRepository.findById.mockResolvedValue(existingPayment);
|
||||
paymentRepository.update.mockResolvedValue(updatedPayment);
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(paymentRepository.findById).toHaveBeenCalledWith('payment-1');
|
||||
expect(paymentRepository.update).toHaveBeenCalled();
|
||||
|
||||
if (result.isOk()) {
|
||||
expect(result.value.payment).toEqual(updatedPayment);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns error when payment not found', async () => {
|
||||
const input: UpdatePaymentStatusInput = {
|
||||
paymentId: 'non-existent',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
};
|
||||
|
||||
paymentRepository.findById.mockResolvedValue(null);
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isErr()).toBe(true);
|
||||
expect(result.unwrapErr().code).toBe('PAYMENT_NOT_FOUND');
|
||||
expect(paymentRepository.update).not.toHaveBeenCalled();
|
||||
expect(output.present).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sets completedAt when status becomes COMPLETED', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
|
||||
|
||||
try {
|
||||
const input: UpdatePaymentStatusInput = {
|
||||
paymentId: 'payment-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
};
|
||||
|
||||
const existingPayment: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'payer-1',
|
||||
payerType: PayerType.SPONSOR,
|
||||
leagueId: 'league-1',
|
||||
status: PaymentStatus.PENDING,
|
||||
createdAt: new Date('2024-12-31T00:00:00.000Z'),
|
||||
};
|
||||
|
||||
paymentRepository.findById.mockResolvedValue(existingPayment);
|
||||
paymentRepository.update.mockImplementation(async (p: Payment) => ({ ...p }));
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
|
||||
expect(paymentRepository.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'payment-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
completedAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
}),
|
||||
);
|
||||
|
||||
const savedPayment = paymentRepository.update.mock.results[0]?.value;
|
||||
await expect(savedPayment).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: 'payment-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
completedAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
}),
|
||||
);
|
||||
|
||||
const presentedPayment = (output.present.mock.calls[0]?.[0] as { payment: Payment }).payment;
|
||||
expect(presentedPayment.status).toBe(PaymentStatus.COMPLETED);
|
||||
expect(presentedPayment.completedAt).toEqual(new Date('2025-01-01T00:00:00.000Z'));
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves completedAt when status is not COMPLETED', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2026-01-01T00:00:00.000Z'));
|
||||
|
||||
try {
|
||||
const input: UpdatePaymentStatusInput = {
|
||||
paymentId: 'payment-1',
|
||||
status: PaymentStatus.FAILED,
|
||||
};
|
||||
|
||||
const existingCompletedAt = new Date('2025-01-01T00:00:00.000Z');
|
||||
|
||||
const existingPayment: Payment = {
|
||||
id: 'payment-1',
|
||||
type: PaymentType.SPONSORSHIP,
|
||||
amount: 100,
|
||||
platformFee: 5,
|
||||
netAmount: 95,
|
||||
payerId: 'payer-1',
|
||||
payerType: PayerType.SPONSOR,
|
||||
leagueId: 'league-1',
|
||||
status: PaymentStatus.COMPLETED,
|
||||
createdAt: new Date('2024-12-31T00:00:00.000Z'),
|
||||
completedAt: existingCompletedAt,
|
||||
};
|
||||
|
||||
paymentRepository.findById.mockResolvedValue(existingPayment);
|
||||
paymentRepository.update.mockImplementation(async (p: Payment) => ({ ...p }));
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
|
||||
expect(paymentRepository.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'payment-1',
|
||||
status: PaymentStatus.FAILED,
|
||||
completedAt: existingCompletedAt,
|
||||
}),
|
||||
);
|
||||
|
||||
const presentedPayment = (output.present.mock.calls[0]?.[0] as { payment: Payment }).payment;
|
||||
expect(presentedPayment.status).toBe(PaymentStatus.FAILED);
|
||||
expect(presentedPayment.completedAt).toEqual(existingCompletedAt);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
if (result.isErr()) {
|
||||
expect(result.error.code).toBe('PAYMENT_NOT_FOUND');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import type { IPaymentRepository } from '../../domain/repositories/IPaymentRepos
|
||||
import type { Payment } from '../../domain/entities/Payment';
|
||||
import { PaymentStatus } from '../../domain/entities/Payment';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
||||
|
||||
@@ -24,14 +23,13 @@ export interface UpdatePaymentStatusResult {
|
||||
}
|
||||
|
||||
export class UpdatePaymentStatusUseCase
|
||||
implements UseCase<UpdatePaymentStatusInput, void, UpdatePaymentStatusErrorCode>
|
||||
implements UseCase<UpdatePaymentStatusInput, UpdatePaymentStatusResult, UpdatePaymentStatusErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly paymentRepository: IPaymentRepository,
|
||||
private readonly output: UseCaseOutputPort<UpdatePaymentStatusResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: UpdatePaymentStatusInput): Promise<Result<void, ApplicationErrorCode<UpdatePaymentStatusErrorCode>>> {
|
||||
async execute(input: UpdatePaymentStatusInput): Promise<Result<UpdatePaymentStatusResult, ApplicationErrorCode<UpdatePaymentStatusErrorCode>>> {
|
||||
const { paymentId, status } = input;
|
||||
|
||||
const existingPayment = await this.paymentRepository.findById(paymentId);
|
||||
@@ -47,8 +45,6 @@ export class UpdatePaymentStatusUseCase
|
||||
|
||||
const savedPayment = await this.paymentRepository.update(updatedPayment as Payment);
|
||||
|
||||
this.output.present({ payment: savedPayment });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ payment: savedPayment });
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,98 @@
|
||||
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
|
||||
import { describe, it, expect, vi, type Mock } from 'vitest';
|
||||
import { UpsertMembershipFeeUseCase, type UpsertMembershipFeeInput } from './UpsertMembershipFeeUseCase';
|
||||
import type { IMembershipFeeRepository } from '../../domain/repositories/IMembershipFeeRepository';
|
||||
import { MembershipFeeType, type MembershipFee } from '../../domain/entities/MembershipFee';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { MembershipFeeType } from '../../domain/entities/MembershipFee';
|
||||
|
||||
describe('UpsertMembershipFeeUseCase', () => {
|
||||
let membershipFeeRepository: {
|
||||
findByLeagueId: Mock;
|
||||
create: Mock;
|
||||
update: Mock;
|
||||
create: Mock;
|
||||
};
|
||||
|
||||
let output: {
|
||||
present: Mock;
|
||||
};
|
||||
|
||||
let useCase: UpsertMembershipFeeUseCase;
|
||||
|
||||
beforeEach(() => {
|
||||
membershipFeeRepository = {
|
||||
findByLeagueId: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
};
|
||||
|
||||
output = {
|
||||
present: vi.fn(),
|
||||
create: vi.fn(),
|
||||
};
|
||||
|
||||
useCase = new UpsertMembershipFeeUseCase(
|
||||
membershipFeeRepository as unknown as IMembershipFeeRepository,
|
||||
output as unknown as UseCaseOutputPort<unknown>,
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a fee when none exists and presents it', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2025-01-01T00:00:00.000Z'));
|
||||
vi.spyOn(Math, 'random').mockReturnValue(0.123456789);
|
||||
it('updates existing membership fee and returns result', async () => {
|
||||
const input: UpsertMembershipFeeInput = {
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
type: MembershipFeeType.MONTHLY,
|
||||
amount: 50,
|
||||
};
|
||||
|
||||
try {
|
||||
const input: UpsertMembershipFeeInput = {
|
||||
leagueId: 'league-1',
|
||||
type: MembershipFeeType.SEASON,
|
||||
amount: 100,
|
||||
};
|
||||
const existingFee = {
|
||||
id: 'fee-1',
|
||||
leagueId: 'league-1',
|
||||
type: MembershipFeeType.YEARLY,
|
||||
amount: 100,
|
||||
enabled: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
membershipFeeRepository.findByLeagueId.mockResolvedValue(null);
|
||||
membershipFeeRepository.create.mockImplementation(async (fee: MembershipFee) => ({ ...fee }));
|
||||
const updatedFee = {
|
||||
...existingFee,
|
||||
type: MembershipFeeType.MONTHLY,
|
||||
amount: 50,
|
||||
seasonId: 'season-1',
|
||||
enabled: true,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
membershipFeeRepository.findByLeagueId.mockResolvedValue(existingFee);
|
||||
membershipFeeRepository.update.mockResolvedValue(updatedFee);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(membershipFeeRepository.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: expect.stringMatching(/^fee-1735689600000-[a-z0-9]{9}$/),
|
||||
leagueId: 'league-1',
|
||||
type: MembershipFeeType.SEASON,
|
||||
amount: 100,
|
||||
enabled: true,
|
||||
createdAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
}),
|
||||
);
|
||||
|
||||
const createdFee = (output.present.mock.calls[0]?.[0] as { fee: MembershipFee }).fee;
|
||||
expect(createdFee.enabled).toBe(true);
|
||||
expect(createdFee.amount).toBe(100);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(membershipFeeRepository.findByLeagueId).toHaveBeenCalledWith('league-1');
|
||||
expect(membershipFeeRepository.update).toHaveBeenCalled();
|
||||
|
||||
if (result.isOk()) {
|
||||
expect(result.value.fee).toEqual(updatedFee);
|
||||
}
|
||||
});
|
||||
|
||||
it('updates an existing fee and sets enabled=false when amount is 0', async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date('2025-01-02T00:00:00.000Z'));
|
||||
it('creates new membership fee and returns result', async () => {
|
||||
const input: UpsertMembershipFeeInput = {
|
||||
leagueId: 'league-1',
|
||||
type: MembershipFeeType.MONTHLY,
|
||||
amount: 50,
|
||||
};
|
||||
|
||||
try {
|
||||
const input: UpsertMembershipFeeInput = {
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-2',
|
||||
type: MembershipFeeType.MONTHLY,
|
||||
amount: 0,
|
||||
};
|
||||
membershipFeeRepository.findByLeagueId.mockResolvedValue(null);
|
||||
|
||||
const existingFee: MembershipFee = {
|
||||
id: 'fee-1',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-1',
|
||||
type: MembershipFeeType.SEASON,
|
||||
amount: 100,
|
||||
enabled: true,
|
||||
createdAt: new Date('2024-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('2024-01-01T00:00:00.000Z'),
|
||||
};
|
||||
const createdFee = {
|
||||
id: 'fee-new',
|
||||
leagueId: 'league-1',
|
||||
type: MembershipFeeType.MONTHLY,
|
||||
amount: 50,
|
||||
enabled: true,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
membershipFeeRepository.findByLeagueId.mockResolvedValue(existingFee);
|
||||
membershipFeeRepository.update.mockImplementation(async (fee: MembershipFee) => ({ ...fee }));
|
||||
membershipFeeRepository.create.mockResolvedValue(createdFee);
|
||||
|
||||
const result = await useCase.execute(input);
|
||||
const result = await useCase.execute(input);
|
||||
|
||||
expect(result.isOk()).toBe(true);
|
||||
|
||||
expect(membershipFeeRepository.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'fee-1',
|
||||
leagueId: 'league-1',
|
||||
seasonId: 'season-2',
|
||||
type: MembershipFeeType.MONTHLY,
|
||||
amount: 0,
|
||||
enabled: false,
|
||||
updatedAt: new Date('2025-01-02T00:00:00.000Z'),
|
||||
}),
|
||||
);
|
||||
|
||||
const updatedFee = (output.present.mock.calls[0]?.[0] as { fee: MembershipFee }).fee;
|
||||
expect(updatedFee.enabled).toBe(false);
|
||||
expect(updatedFee.amount).toBe(0);
|
||||
expect(updatedFee.seasonId).toBe('season-2');
|
||||
expect(updatedFee.type).toBe(MembershipFeeType.MONTHLY);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
expect(result.isOk()).toBe(true);
|
||||
expect(membershipFeeRepository.findByLeagueId).toHaveBeenCalledWith('league-1');
|
||||
expect(membershipFeeRepository.create).toHaveBeenCalled();
|
||||
|
||||
if (result.isOk()) {
|
||||
expect(result.value.fee).toEqual(createdFee);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,6 @@
|
||||
import type { IMembershipFeeRepository } from '../../domain/repositories/IMembershipFeeRepository';
|
||||
import type { MembershipFeeType, MembershipFee } from '../../domain/entities/MembershipFee';
|
||||
import type { UseCase } from '@core/shared/application/UseCase';
|
||||
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
|
||||
import { Result } from '@core/shared/application/Result';
|
||||
|
||||
export interface UpsertMembershipFeeInput {
|
||||
@@ -24,14 +23,13 @@ export interface UpsertMembershipFeeResult {
|
||||
export type UpsertMembershipFeeErrorCode = never;
|
||||
|
||||
export class UpsertMembershipFeeUseCase
|
||||
implements UseCase<UpsertMembershipFeeInput, void, UpsertMembershipFeeErrorCode>
|
||||
implements UseCase<UpsertMembershipFeeInput, UpsertMembershipFeeResult, UpsertMembershipFeeErrorCode>
|
||||
{
|
||||
constructor(
|
||||
private readonly membershipFeeRepository: IMembershipFeeRepository,
|
||||
private readonly output: UseCaseOutputPort<UpsertMembershipFeeResult>,
|
||||
) {}
|
||||
|
||||
async execute(input: UpsertMembershipFeeInput): Promise<Result<void, never>> {
|
||||
async execute(input: UpsertMembershipFeeInput): Promise<Result<UpsertMembershipFeeResult, never>> {
|
||||
const { leagueId, seasonId, type, amount } = input;
|
||||
|
||||
let existingFee = await this.membershipFeeRepository.findByLeagueId(leagueId);
|
||||
@@ -59,8 +57,6 @@ export class UpsertMembershipFeeUseCase
|
||||
fee = await this.membershipFeeRepository.create(newFee);
|
||||
}
|
||||
|
||||
this.output.present({ fee });
|
||||
|
||||
return Result.ok(undefined);
|
||||
return Result.ok({ fee });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user