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); }); }); });