import { describe, it, expect } from 'vitest'; import { DomainService, DomainCalculationService, ResultDomainCalculationService, DomainValidationService, DomainFactoryService, DomainServiceAlias, DomainCalculationServiceAlias, ResultDomainCalculationServiceAlias, DomainValidationServiceAlias, DomainFactoryServiceAlias } from './Service'; import { Result } from './Result'; describe('Service', () => { describe('DomainService interface', () => { it('should have optional serviceName property', () => { const service: DomainService = { serviceName: 'TestService' }; expect(service.serviceName).toBe('TestService'); }); it('should work without serviceName', () => { const service: DomainService = {}; expect(service.serviceName).toBeUndefined(); }); it('should support different service implementations', () => { const service1: DomainService = { serviceName: 'Service1' }; const service2: DomainService = { serviceName: 'Service2' }; const service3: DomainService = {}; expect(service1.serviceName).toBe('Service1'); expect(service2.serviceName).toBe('Service2'); expect(service3.serviceName).toBeUndefined(); }); }); describe('DomainCalculationService interface', () => { it('should have calculate method', () => { const service: DomainCalculationService = { calculate: (input: number) => input * 2 }; expect(service.calculate(5)).toBe(10); }); it('should support different input and output types', () => { const stringService: DomainCalculationService = { calculate: (input: string) => input.toUpperCase() }; const objectService: DomainCalculationService<{ x: number; y: number }, number> = { calculate: (input) => input.x + input.y }; expect(stringService.calculate('hello')).toBe('HELLO'); expect(objectService.calculate({ x: 3, y: 4 })).toBe(7); }); it('should support complex calculations', () => { interface CalculationInput { values: number[]; operation: 'sum' | 'average' | 'max'; } const calculator: DomainCalculationService = { calculate: (input) => { switch (input.operation) { case 'sum': return input.values.reduce((a, b) => a + b, 0); case 'average': return input.values.reduce((a, b) => a + b, 0) / input.values.length; case 'max': return Math.max(...input.values); } } }; expect(calculator.calculate({ values: [1, 2, 3], operation: 'sum' })).toBe(6); expect(calculator.calculate({ values: [1, 2, 3], operation: 'average' })).toBe(2); expect(calculator.calculate({ values: [1, 2, 3], operation: 'max' })).toBe(3); }); }); describe('ResultDomainCalculationService interface', () => { it('should have calculate method returning Result', () => { const service: ResultDomainCalculationService = { calculate: (input: number) => { if (input < 0) { return Result.err('Input must be non-negative'); } return Result.ok(input * 2); } }; const successResult = service.calculate(5); expect(successResult.isOk()).toBe(true); expect(successResult.unwrap()).toBe(10); const errorResult = service.calculate(-1); expect(errorResult.isErr()).toBe(true); expect(errorResult.unwrapErr()).toBe('Input must be non-negative'); }); it('should support validation logic', () => { interface ValidationResult { isValid: boolean; errors: string[]; } const validator: ResultDomainCalculationService = { calculate: (input: string) => { const errors: string[] = []; if (input.length < 3) { errors.push('Must be at least 3 characters'); } if (!input.match(/^[a-zA-Z]+$/)) { errors.push('Must contain only letters'); } if (errors.length > 0) { return Result.err(errors.join(', ')); } return Result.ok({ isValid: true, errors: [] }); } }; const validResult = validator.calculate('Hello'); expect(validResult.isOk()).toBe(true); expect(validResult.unwrap()).toEqual({ isValid: true, errors: [] }); const invalidResult = validator.calculate('ab'); expect(invalidResult.isErr()).toBe(true); expect(invalidResult.unwrapErr()).toBe('Must be at least 3 characters'); }); it('should support complex business rules', () => { interface DiscountInput { basePrice: number; customerType: 'regular' | 'premium' | 'vip'; hasCoupon: boolean; } const discountCalculator: ResultDomainCalculationService = { calculate: (input) => { let discount = 0; // Customer type discount switch (input.customerType) { case 'premium': discount += 0.1; break; case 'vip': discount += 0.2; break; } // Coupon discount if (input.hasCoupon) { discount += 0.05; } // Validate price if (input.basePrice <= 0) { return Result.err('Price must be positive'); } const finalPrice = input.basePrice * (1 - discount); return Result.ok(finalPrice); } }; const vipWithCoupon = discountCalculator.calculate({ basePrice: 100, customerType: 'vip', hasCoupon: true }); expect(vipWithCoupon.isOk()).toBe(true); expect(vipWithCoupon.unwrap()).toBe(75); // 100 * (1 - 0.2 - 0.05) = 75 const invalidPrice = discountCalculator.calculate({ basePrice: 0, customerType: 'regular', hasCoupon: false }); expect(invalidPrice.isErr()).toBe(true); expect(invalidPrice.unwrapErr()).toBe('Price must be positive'); }); }); describe('DomainValidationService interface', () => { it('should have validate method returning Result', () => { const service: DomainValidationService = { validate: (input: string) => { if (input.length === 0) { return Result.err('Input cannot be empty'); } return Result.ok(true); } }; const validResult = service.validate('test'); expect(validResult.isOk()).toBe(true); expect(validResult.unwrap()).toBe(true); const invalidResult = service.validate(''); expect(invalidResult.isErr()).toBe(true); expect(invalidResult.unwrapErr()).toBe('Input cannot be empty'); }); it('should support validation of complex objects', () => { interface UserInput { email: string; password: string; age: number; } const userValidator: DomainValidationService = { validate: (input) => { const errors: string[] = []; if (!input.email.includes('@')) { errors.push('Invalid email format'); } if (input.password.length < 8) { errors.push('Password must be at least 8 characters'); } if (input.age < 18) { errors.push('Must be at least 18 years old'); } if (errors.length > 0) { return Result.err(errors.join(', ')); } return Result.ok(input); } }; const validUser = userValidator.validate({ email: 'john@example.com', password: 'securepassword', age: 25 }); expect(validUser.isOk()).toBe(true); expect(validUser.unwrap()).toEqual({ email: 'john@example.com', password: 'securepassword', age: 25 }); const invalidUser = userValidator.validate({ email: 'invalid-email', password: 'short', age: 15 }); expect(invalidUser.isErr()).toBe(true); expect(invalidUser.unwrapErr()).toContain('Invalid email format'); expect(invalidUser.unwrapErr()).toContain('Password must be at least 8 characters'); expect(invalidUser.unwrapErr()).toContain('Must be at least 18 years old'); }); }); describe('DomainFactoryService interface', () => { it('should have create method', () => { const service: DomainFactoryService = { create: (input: string) => ({ id: input.length, value: input.toUpperCase() }) }; const result = service.create('test'); expect(result).toEqual({ id: 4, value: 'TEST' }); }); it('should support creating complex objects', () => { interface CreateUserInput { name: string; email: string; } interface User { id: string; name: string; email: string; createdAt: Date; } const userFactory: DomainFactoryService = { create: (input) => ({ id: `user-${Date.now()}`, name: input.name, email: input.email, createdAt: new Date() }) }; const user = userFactory.create({ name: 'John Doe', email: 'john@example.com' }); expect(user.id).toMatch(/^user-\d+$/); expect(user.name).toBe('John Doe'); expect(user.email).toBe('john@example.com'); expect(user.createdAt).toBeInstanceOf(Date); }); it('should support creating value objects', () => { interface AddressProps { street: string; city: string; zipCode: string; } const addressFactory: DomainFactoryService = { create: (input) => ({ street: input.street.trim(), city: input.city.trim(), zipCode: input.zipCode.trim() }) }; const address = addressFactory.create({ street: ' 123 Main St ', city: ' New York ', zipCode: ' 10001 ' }); expect(address.street).toBe('123 Main St'); expect(address.city).toBe('New York'); expect(address.zipCode).toBe('10001'); }); }); describe('ServiceAlias types', () => { it('should be assignable to their base interfaces', () => { const service1: DomainServiceAlias = { serviceName: 'Test' }; const service2: DomainCalculationServiceAlias = { calculate: (x) => x * 2 }; const service3: ResultDomainCalculationServiceAlias = { calculate: (x) => Result.ok(x * 2) }; const service4: DomainValidationServiceAlias = { validate: (x) => Result.ok(x.length > 0) }; const service5: DomainFactoryServiceAlias = { create: (x) => x.toUpperCase() }; expect(service1.serviceName).toBe('Test'); expect(service2.calculate(5)).toBe(10); expect(service3.calculate(5).isOk()).toBe(true); expect(service4.validate('test').isOk()).toBe(true); expect(service5.create('test')).toBe('TEST'); }); }); });