164 lines
6.1 KiB
TypeScript
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 { CheckoutServicePort } from '@gridpilot/automation/application/ports/CheckoutServicePort';
|
|
import type { CheckoutConfirmationPort } from '@gridpilot/automation/application/ports/CheckoutConfirmationPort';
|
|
|
|
describe('ConfirmCheckoutUseCase - Enhanced with Confirmation Port', () => {
|
|
let mockCheckoutService: CheckoutServicePort;
|
|
let mockConfirmationPort: CheckoutConfirmationPort;
|
|
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);
|
|
});
|
|
});
|
|
}); |