164 lines
6.1 KiB
TypeScript
164 lines
6.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { ConfirmCheckoutUseCase } from '@/packages/application/use-cases/ConfirmCheckoutUseCase';
|
|
import { Result } from '@/packages/shared/result/Result';
|
|
import { CheckoutPrice } from '@/packages/domain/value-objects/CheckoutPrice';
|
|
import { CheckoutState } from '@/packages/domain/value-objects/CheckoutState';
|
|
import { CheckoutConfirmation } from '@/packages/domain/value-objects/CheckoutConfirmation';
|
|
import type { ICheckoutService } from '@/packages/application/ports/ICheckoutService';
|
|
import type { ICheckoutConfirmationPort } from '@/packages/application/ports/ICheckoutConfirmationPort';
|
|
|
|
describe('ConfirmCheckoutUseCase - Enhanced with Confirmation Port', () => {
|
|
let mockCheckoutService: ICheckoutService;
|
|
let mockConfirmationPort: ICheckoutConfirmationPort;
|
|
let useCase: ConfirmCheckoutUseCase;
|
|
|
|
beforeEach(() => {
|
|
mockCheckoutService = {
|
|
extractCheckoutInfo: vi.fn(),
|
|
proceedWithCheckout: vi.fn(),
|
|
};
|
|
|
|
mockConfirmationPort = {
|
|
requestCheckoutConfirmation: vi.fn(),
|
|
};
|
|
|
|
useCase = new ConfirmCheckoutUseCase(mockCheckoutService, mockConfirmationPort);
|
|
});
|
|
|
|
describe('with new confirmation flow', () => {
|
|
it('should extract price, request confirmation via port, then proceed', async () => {
|
|
const price = CheckoutPrice.fromString('$25.50');
|
|
const state = CheckoutState.ready();
|
|
|
|
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
|
Result.ok({ price, state, buttonHtml: '' })
|
|
);
|
|
|
|
vi.mocked(mockConfirmationPort.requestCheckoutConfirmation).mockResolvedValue(
|
|
Result.ok(CheckoutConfirmation.create('confirmed'))
|
|
);
|
|
|
|
vi.mocked(mockCheckoutService.proceedWithCheckout).mockResolvedValue(
|
|
Result.ok(undefined)
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(mockCheckoutService.extractCheckoutInfo).toHaveBeenCalled();
|
|
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
price: expect.any(CheckoutPrice),
|
|
state: expect.any(CheckoutState),
|
|
})
|
|
);
|
|
expect(mockCheckoutService.proceedWithCheckout).toHaveBeenCalled();
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should not proceed if user cancels confirmation', async () => {
|
|
const price = CheckoutPrice.fromString('$10.00');
|
|
const state = CheckoutState.ready();
|
|
|
|
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
|
Result.ok({ price, state, buttonHtml: '' })
|
|
);
|
|
|
|
vi.mocked(mockConfirmationPort.requestCheckoutConfirmation).mockResolvedValue(
|
|
Result.ok(CheckoutConfirmation.create('cancelled'))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalled();
|
|
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr().message).toContain('cancelled');
|
|
});
|
|
|
|
it('should not proceed if confirmation times out', async () => {
|
|
const price = CheckoutPrice.fromString('$10.00');
|
|
const state = CheckoutState.ready();
|
|
|
|
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
|
Result.ok({ price, state, buttonHtml: '' })
|
|
);
|
|
|
|
vi.mocked(mockConfirmationPort.requestCheckoutConfirmation).mockResolvedValue(
|
|
Result.ok(CheckoutConfirmation.create('timeout'))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalled();
|
|
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr().message).toContain('timeout');
|
|
});
|
|
|
|
it('should fail if confirmation port returns error', async () => {
|
|
const price = CheckoutPrice.fromString('$10.00');
|
|
const state = CheckoutState.ready();
|
|
|
|
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
|
Result.ok({ price, state, buttonHtml: '' })
|
|
);
|
|
|
|
vi.mocked(mockConfirmationPort.requestCheckoutConfirmation).mockResolvedValue(
|
|
Result.err(new Error('IPC communication failed'))
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr().message).toContain('IPC communication failed');
|
|
});
|
|
|
|
it('should still reject insufficient funds before confirmation', async () => {
|
|
const price = CheckoutPrice.fromString('$10.00');
|
|
const state = CheckoutState.insufficientFunds();
|
|
|
|
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
|
Result.ok({ price, state, buttonHtml: '' })
|
|
);
|
|
|
|
const result = await useCase.execute();
|
|
|
|
expect(mockConfirmationPort.requestCheckoutConfirmation).not.toHaveBeenCalled();
|
|
expect(mockCheckoutService.proceedWithCheckout).not.toHaveBeenCalled();
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr().message).toContain('Insufficient funds');
|
|
});
|
|
|
|
it('should pass session metadata to confirmation port', async () => {
|
|
const price = CheckoutPrice.fromString('$25.50');
|
|
const state = CheckoutState.ready();
|
|
const sessionMetadata = {
|
|
sessionName: 'Test Race',
|
|
trackId: 'spa',
|
|
carIds: ['car1', 'car2'],
|
|
};
|
|
|
|
vi.mocked(mockCheckoutService.extractCheckoutInfo).mockResolvedValue(
|
|
Result.ok({ price, state, buttonHtml: '' })
|
|
);
|
|
|
|
vi.mocked(mockConfirmationPort.requestCheckoutConfirmation).mockResolvedValue(
|
|
Result.ok(CheckoutConfirmation.create('confirmed'))
|
|
);
|
|
|
|
vi.mocked(mockCheckoutService.proceedWithCheckout).mockResolvedValue(
|
|
Result.ok(undefined)
|
|
);
|
|
|
|
const result = await useCase.execute(sessionMetadata);
|
|
|
|
expect(mockConfirmationPort.requestCheckoutConfirmation).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
sessionMetadata,
|
|
timeoutMs: expect.any(Number),
|
|
})
|
|
);
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
});
|
|
}); |