180 lines
6.0 KiB
TypeScript
180 lines
6.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { Result } from '@/packages/shared/result/Result';
|
|
import { CheckoutConfirmation } from '@/packages/domain/value-objects/CheckoutConfirmation';
|
|
import { CheckoutPrice } from '@/packages/domain/value-objects/CheckoutPrice';
|
|
import { CheckoutState } from '@/packages/domain/value-objects/CheckoutState';
|
|
|
|
/**
|
|
* Contract tests for ICheckoutConfirmationPort
|
|
*
|
|
* Any implementation must:
|
|
* 1. Accept CheckoutConfirmationRequest with price, state, sessionMetadata, timeoutMs
|
|
* 2. Return Result<CheckoutConfirmation> with decision: confirmed, cancelled, or timeout
|
|
* 3. Handle timeout gracefully by returning timeout decision
|
|
* 4. Validate request parameters before processing
|
|
*/
|
|
|
|
export interface CheckoutConfirmationRequest {
|
|
price: CheckoutPrice;
|
|
state: CheckoutState;
|
|
sessionMetadata: {
|
|
sessionName: string;
|
|
trackId: string;
|
|
carIds: string[];
|
|
};
|
|
timeoutMs: number;
|
|
}
|
|
|
|
export interface ICheckoutConfirmationPort {
|
|
requestCheckoutConfirmation(
|
|
request: CheckoutConfirmationRequest
|
|
): Promise<Result<CheckoutConfirmation>>;
|
|
}
|
|
|
|
describe('ICheckoutConfirmationPort contract', () => {
|
|
it('should define the required interface structure', () => {
|
|
// This test verifies the port interface contract exists
|
|
const mockPort: ICheckoutConfirmationPort = {
|
|
requestCheckoutConfirmation: async (_request: CheckoutConfirmationRequest) => {
|
|
return Result.ok(CheckoutConfirmation.create('confirmed'));
|
|
},
|
|
};
|
|
|
|
expect(mockPort.requestCheckoutConfirmation).toBeDefined();
|
|
expect(typeof mockPort.requestCheckoutConfirmation).toBe('function');
|
|
});
|
|
|
|
it('should accept valid CheckoutConfirmationRequest', async () => {
|
|
const mockPort: ICheckoutConfirmationPort = {
|
|
requestCheckoutConfirmation: async (request: CheckoutConfirmationRequest) => {
|
|
expect(request.price).toBeInstanceOf(CheckoutPrice);
|
|
expect(request.state).toBeInstanceOf(CheckoutState);
|
|
expect(request.sessionMetadata).toBeDefined();
|
|
expect(request.sessionMetadata.sessionName).toBeTruthy();
|
|
expect(request.sessionMetadata.trackId).toBeTruthy();
|
|
expect(Array.isArray(request.sessionMetadata.carIds)).toBe(true);
|
|
expect(request.timeoutMs).toBeGreaterThan(0);
|
|
return Result.ok(CheckoutConfirmation.create('confirmed'));
|
|
},
|
|
};
|
|
|
|
const request: CheckoutConfirmationRequest = {
|
|
price: CheckoutPrice.fromString('$10.00'),
|
|
state: CheckoutState.ready(),
|
|
sessionMetadata: {
|
|
sessionName: 'Test Session',
|
|
trackId: 'spa',
|
|
carIds: ['car1', 'car2'],
|
|
},
|
|
timeoutMs: 30000,
|
|
};
|
|
|
|
const result = await mockPort.requestCheckoutConfirmation(request);
|
|
expect(result.isOk()).toBe(true);
|
|
});
|
|
|
|
it('should return Result with CheckoutConfirmation on success', async () => {
|
|
const mockPort: ICheckoutConfirmationPort = {
|
|
requestCheckoutConfirmation: async () => {
|
|
return Result.ok(CheckoutConfirmation.create('confirmed'));
|
|
},
|
|
};
|
|
|
|
const request: CheckoutConfirmationRequest = {
|
|
price: CheckoutPrice.fromString('$10.00'),
|
|
state: CheckoutState.ready(),
|
|
sessionMetadata: {
|
|
sessionName: 'Test Session',
|
|
trackId: 'spa',
|
|
carIds: ['car1'],
|
|
},
|
|
timeoutMs: 30000,
|
|
};
|
|
|
|
const result = await mockPort.requestCheckoutConfirmation(request);
|
|
expect(result.isOk()).toBe(true);
|
|
|
|
const confirmation = result.unwrap();
|
|
expect(confirmation).toBeInstanceOf(CheckoutConfirmation);
|
|
expect(confirmation.isConfirmed()).toBe(true);
|
|
});
|
|
|
|
it('should support cancelled decision', async () => {
|
|
const mockPort: ICheckoutConfirmationPort = {
|
|
requestCheckoutConfirmation: async () => {
|
|
return Result.ok(CheckoutConfirmation.create('cancelled'));
|
|
},
|
|
};
|
|
|
|
const request: CheckoutConfirmationRequest = {
|
|
price: CheckoutPrice.fromString('$10.00'),
|
|
state: CheckoutState.ready(),
|
|
sessionMetadata: {
|
|
sessionName: 'Test Session',
|
|
trackId: 'spa',
|
|
carIds: ['car1'],
|
|
},
|
|
timeoutMs: 30000,
|
|
};
|
|
|
|
const result = await mockPort.requestCheckoutConfirmation(request);
|
|
expect(result.isOk()).toBe(true);
|
|
|
|
const confirmation = result.unwrap();
|
|
expect(confirmation.isCancelled()).toBe(true);
|
|
});
|
|
|
|
it('should support timeout decision', async () => {
|
|
const mockPort: ICheckoutConfirmationPort = {
|
|
requestCheckoutConfirmation: async () => {
|
|
return Result.ok(CheckoutConfirmation.create('timeout'));
|
|
},
|
|
};
|
|
|
|
const request: CheckoutConfirmationRequest = {
|
|
price: CheckoutPrice.fromString('$10.00'),
|
|
state: CheckoutState.ready(),
|
|
sessionMetadata: {
|
|
sessionName: 'Test Session',
|
|
trackId: 'spa',
|
|
carIds: ['car1'],
|
|
},
|
|
timeoutMs: 1000,
|
|
};
|
|
|
|
const result = await mockPort.requestCheckoutConfirmation(request);
|
|
expect(result.isOk()).toBe(true);
|
|
|
|
const confirmation = result.unwrap();
|
|
expect(confirmation.isTimeout()).toBe(true);
|
|
});
|
|
|
|
it('should return error Result for invalid requests', async () => {
|
|
const mockPort: ICheckoutConfirmationPort = {
|
|
requestCheckoutConfirmation: async (request: CheckoutConfirmationRequest) => {
|
|
if (request.timeoutMs <= 0) {
|
|
return Result.err(new Error('Timeout must be positive'));
|
|
}
|
|
if (!request.sessionMetadata.sessionName) {
|
|
return Result.err(new Error('Session name is required'));
|
|
}
|
|
return Result.ok(CheckoutConfirmation.create('confirmed'));
|
|
},
|
|
};
|
|
|
|
const invalidRequest: CheckoutConfirmationRequest = {
|
|
price: CheckoutPrice.fromString('$10.00'),
|
|
state: CheckoutState.ready(),
|
|
sessionMetadata: {
|
|
sessionName: '',
|
|
trackId: 'spa',
|
|
carIds: ['car1'],
|
|
},
|
|
timeoutMs: 30000,
|
|
};
|
|
|
|
const result = await mockPort.requestCheckoutConfirmation(invalidRequest);
|
|
expect(result.isErr()).toBe(true);
|
|
expect(result.unwrapErr().message).toContain('Session name');
|
|
});
|
|
}); |