Files
gridpilot.gg/tests/unit/application/use-cases/ConfirmCheckoutUseCase.enhanced.test.ts
2025-12-04 17:07:59 +01:00

164 lines
6.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ConfirmCheckoutUseCase } from '@gridpilot/automation/application/use-cases/ConfirmCheckoutUseCase';
import { Result } from '@gridpilot/shared-result';
import { CheckoutPrice } from '@gridpilot/automation/domain/value-objects/CheckoutPrice';
import { CheckoutState } from '@gridpilot/automation/domain/value-objects/CheckoutState';
import { CheckoutConfirmation } from '@gridpilot/automation/domain/value-objects/CheckoutConfirmation';
import type { ICheckoutService } from '@gridpilot/automation/application/ports/ICheckoutService';
import type { ICheckoutConfirmationPort } from '@gridpilot/automation/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);
});
});
});