336 lines
11 KiB
TypeScript
336 lines
11 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { ApplicationErrorCode } from './ApplicationErrorCode';
|
|
|
|
describe('ApplicationErrorCode', () => {
|
|
describe('ApplicationErrorCode type', () => {
|
|
it('should create error code with code only', () => {
|
|
const errorCode: ApplicationErrorCode<'USER_NOT_FOUND'> = {
|
|
code: 'USER_NOT_FOUND'
|
|
};
|
|
|
|
expect(errorCode.code).toBe('USER_NOT_FOUND');
|
|
});
|
|
|
|
it('should create error code with code and details', () => {
|
|
const errorCode: ApplicationErrorCode<'INSUFFICIENT_FUNDS', { balance: number; required: number }> = {
|
|
code: 'INSUFFICIENT_FUNDS',
|
|
details: { balance: 50, required: 100 }
|
|
};
|
|
|
|
expect(errorCode.code).toBe('INSUFFICIENT_FUNDS');
|
|
expect(errorCode.details).toEqual({ balance: 50, required: 100 });
|
|
});
|
|
|
|
it('should support different error code types', () => {
|
|
const notFoundCode: ApplicationErrorCode<'USER_NOT_FOUND'> = {
|
|
code: 'USER_NOT_FOUND'
|
|
};
|
|
|
|
const validationCode: ApplicationErrorCode<'VALIDATION_ERROR', { field: string }> = {
|
|
code: 'VALIDATION_ERROR',
|
|
details: { field: 'email' }
|
|
};
|
|
|
|
const permissionCode: ApplicationErrorCode<'PERMISSION_DENIED', { resource: string }> = {
|
|
code: 'PERMISSION_DENIED',
|
|
details: { resource: 'admin-panel' }
|
|
};
|
|
|
|
expect(notFoundCode.code).toBe('USER_NOT_FOUND');
|
|
expect(validationCode.code).toBe('VALIDATION_ERROR');
|
|
expect(validationCode.details).toEqual({ field: 'email' });
|
|
expect(permissionCode.code).toBe('PERMISSION_DENIED');
|
|
expect(permissionCode.details).toEqual({ resource: 'admin-panel' });
|
|
});
|
|
|
|
it('should support complex details types', () => {
|
|
interface PaymentErrorDetails {
|
|
amount: number;
|
|
currency: string;
|
|
retryAfter?: number;
|
|
attempts: number;
|
|
}
|
|
|
|
const paymentErrorCode: ApplicationErrorCode<'PAYMENT_FAILED', PaymentErrorDetails> = {
|
|
code: 'PAYMENT_FAILED',
|
|
details: {
|
|
amount: 100,
|
|
currency: 'USD',
|
|
retryAfter: 60,
|
|
attempts: 3
|
|
}
|
|
};
|
|
|
|
expect(paymentErrorCode.code).toBe('PAYMENT_FAILED');
|
|
expect(paymentErrorCode.details).toEqual({
|
|
amount: 100,
|
|
currency: 'USD',
|
|
retryAfter: 60,
|
|
attempts: 3
|
|
});
|
|
});
|
|
|
|
it('should support optional details', () => {
|
|
const errorCodeWithDetails: ApplicationErrorCode<'ERROR', { message: string }> = {
|
|
code: 'ERROR',
|
|
details: { message: 'Something went wrong' }
|
|
};
|
|
|
|
const errorCodeWithoutDetails: ApplicationErrorCode<'ERROR', undefined> = {
|
|
code: 'ERROR'
|
|
};
|
|
|
|
expect(errorCodeWithDetails.code).toBe('ERROR');
|
|
expect(errorCodeWithDetails.details).toEqual({ message: 'Something went wrong' });
|
|
expect(errorCodeWithoutDetails.code).toBe('ERROR');
|
|
});
|
|
});
|
|
|
|
describe('ApplicationErrorCode behavior', () => {
|
|
it('should be assignable to Result error type', () => {
|
|
// ApplicationErrorCode is designed to be used with Result type
|
|
// This test verifies the type compatibility
|
|
type MyErrorCodes = 'USER_NOT_FOUND' | 'VALIDATION_ERROR' | 'PERMISSION_DENIED';
|
|
|
|
const userNotFound: ApplicationErrorCode<MyErrorCodes> = {
|
|
code: 'USER_NOT_FOUND'
|
|
};
|
|
|
|
const validationError: ApplicationErrorCode<MyErrorCodes, { field: string }> = {
|
|
code: 'VALIDATION_ERROR',
|
|
details: { field: 'email' }
|
|
};
|
|
|
|
const permissionError: ApplicationErrorCode<MyErrorCodes, { resource: string }> = {
|
|
code: 'PERMISSION_DENIED',
|
|
details: { resource: 'admin-panel' }
|
|
};
|
|
|
|
expect(userNotFound.code).toBe('USER_NOT_FOUND');
|
|
expect(validationError.code).toBe('VALIDATION_ERROR');
|
|
expect(validationError.details).toEqual({ field: 'email' });
|
|
expect(permissionError.code).toBe('PERMISSION_DENIED');
|
|
expect(permissionError.details).toEqual({ resource: 'admin-panel' });
|
|
});
|
|
|
|
it('should support error code patterns', () => {
|
|
// Common error code patterns
|
|
const notFoundPattern: ApplicationErrorCode<'NOT_FOUND', { resource: string; id?: string }> = {
|
|
code: 'NOT_FOUND',
|
|
details: { resource: 'user', id: '123' }
|
|
};
|
|
|
|
const conflictPattern: ApplicationErrorCode<'CONFLICT', { resource: string; existingId: string }> = {
|
|
code: 'CONFLICT',
|
|
details: { resource: 'order', existingId: '456' }
|
|
};
|
|
|
|
const validationPattern: ApplicationErrorCode<'VALIDATION_ERROR', { field: string; value: unknown; reason: string }> = {
|
|
code: 'VALIDATION_ERROR',
|
|
details: { field: 'email', value: 'invalid', reason: 'must contain @' }
|
|
};
|
|
|
|
expect(notFoundPattern.code).toBe('NOT_FOUND');
|
|
expect(notFoundPattern.details).toEqual({ resource: 'user', id: '123' });
|
|
expect(conflictPattern.code).toBe('CONFLICT');
|
|
expect(conflictPattern.details).toEqual({ resource: 'order', existingId: '456' });
|
|
expect(validationPattern.code).toBe('VALIDATION_ERROR');
|
|
expect(validationPattern.details).toEqual({ field: 'email', value: 'invalid', reason: 'must contain @' });
|
|
});
|
|
|
|
it('should support error code with metadata', () => {
|
|
interface ErrorMetadata {
|
|
timestamp: string;
|
|
requestId?: string;
|
|
userId?: string;
|
|
sessionId?: string;
|
|
}
|
|
|
|
const errorCode: ApplicationErrorCode<'AUTH_ERROR', ErrorMetadata> = {
|
|
code: 'AUTH_ERROR',
|
|
details: {
|
|
timestamp: new Date().toISOString(),
|
|
requestId: 'req-123',
|
|
userId: 'user-456',
|
|
sessionId: 'session-789'
|
|
}
|
|
};
|
|
|
|
expect(errorCode.code).toBe('AUTH_ERROR');
|
|
expect(errorCode.details).toBeDefined();
|
|
expect(errorCode.details?.timestamp).toBeDefined();
|
|
expect(errorCode.details?.requestId).toBe('req-123');
|
|
});
|
|
|
|
it('should support error code with retry information', () => {
|
|
interface RetryInfo {
|
|
retryAfter: number;
|
|
maxRetries: number;
|
|
currentAttempt: number;
|
|
}
|
|
|
|
const retryableError: ApplicationErrorCode<'RATE_LIMIT_EXCEEDED', RetryInfo> = {
|
|
code: 'RATE_LIMIT_EXCEEDED',
|
|
details: {
|
|
retryAfter: 60,
|
|
maxRetries: 3,
|
|
currentAttempt: 1
|
|
}
|
|
};
|
|
|
|
expect(retryableError.code).toBe('RATE_LIMIT_EXCEEDED');
|
|
expect(retryableError.details).toEqual({
|
|
retryAfter: 60,
|
|
maxRetries: 3,
|
|
currentAttempt: 1
|
|
});
|
|
});
|
|
|
|
it('should support error code with validation details', () => {
|
|
interface ValidationErrorDetails {
|
|
field: string;
|
|
value: unknown;
|
|
constraints: string[];
|
|
message: string;
|
|
}
|
|
|
|
const validationError: ApplicationErrorCode<'VALIDATION_ERROR', ValidationErrorDetails> = {
|
|
code: 'VALIDATION_ERROR',
|
|
details: {
|
|
field: 'email',
|
|
value: 'invalid-email',
|
|
constraints: ['must be a valid email', 'must not be empty'],
|
|
message: 'Email validation failed'
|
|
}
|
|
};
|
|
|
|
expect(validationError.code).toBe('VALIDATION_ERROR');
|
|
expect(validationError.details).toEqual({
|
|
field: 'email',
|
|
value: 'invalid-email',
|
|
constraints: ['must be a valid email', 'must not be empty'],
|
|
message: 'Email validation failed'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('ApplicationErrorCode implementation patterns', () => {
|
|
it('should support error code factory pattern', () => {
|
|
function createErrorCode<Code extends string, Details = unknown>(
|
|
code: Code,
|
|
details?: Details
|
|
): ApplicationErrorCode<Code, Details> {
|
|
return details ? { code, details } : { code };
|
|
}
|
|
|
|
const notFound = createErrorCode('USER_NOT_FOUND');
|
|
const validation = createErrorCode('VALIDATION_ERROR', { field: 'email' });
|
|
const permission = createErrorCode('PERMISSION_DENIED', { resource: 'admin' });
|
|
|
|
expect(notFound.code).toBe('USER_NOT_FOUND');
|
|
expect(validation.code).toBe('VALIDATION_ERROR');
|
|
expect(validation.details).toEqual({ field: 'email' });
|
|
expect(permission.code).toBe('PERMISSION_DENIED');
|
|
expect(permission.details).toEqual({ resource: 'admin' });
|
|
});
|
|
|
|
it('should support error code builder pattern', () => {
|
|
class ErrorCodeBuilder<Code extends string, Details = unknown> {
|
|
private code: Code = '' as Code;
|
|
private details?: Details;
|
|
|
|
withCode(code: Code): this {
|
|
this.code = code;
|
|
return this;
|
|
}
|
|
|
|
withDetails(details: Details): this {
|
|
this.details = details;
|
|
return this;
|
|
}
|
|
|
|
build(): ApplicationErrorCode<Code, Details> {
|
|
return this.details ? { code: this.code, details: this.details } : { code: this.code };
|
|
}
|
|
}
|
|
|
|
const errorCode = new ErrorCodeBuilder<'USER_NOT_FOUND'>()
|
|
.withCode('USER_NOT_FOUND')
|
|
.build();
|
|
|
|
const errorCodeWithDetails = new ErrorCodeBuilder<'VALIDATION_ERROR', { field: string }>()
|
|
.withCode('VALIDATION_ERROR')
|
|
.withDetails({ field: 'email' })
|
|
.build();
|
|
|
|
expect(errorCode.code).toBe('USER_NOT_FOUND');
|
|
expect(errorCodeWithDetails.code).toBe('VALIDATION_ERROR');
|
|
expect(errorCodeWithDetails.details).toEqual({ field: 'email' });
|
|
});
|
|
|
|
it('should support error code categorization', () => {
|
|
const errorCodes: ApplicationErrorCode<string, unknown>[] = [
|
|
{ code: 'USER_NOT_FOUND' },
|
|
{ code: 'VALIDATION_ERROR', details: { field: 'email' } },
|
|
{ code: 'PERMISSION_DENIED', details: { resource: 'admin' } },
|
|
{ code: 'NETWORK_ERROR' }
|
|
];
|
|
|
|
const notFoundCodes = errorCodes.filter(e => e.code === 'USER_NOT_FOUND');
|
|
const validationCodes = errorCodes.filter(e => e.code === 'VALIDATION_ERROR');
|
|
const permissionCodes = errorCodes.filter(e => e.code === 'PERMISSION_DENIED');
|
|
const networkCodes = errorCodes.filter(e => e.code === 'NETWORK_ERROR');
|
|
|
|
expect(notFoundCodes).toHaveLength(1);
|
|
expect(validationCodes).toHaveLength(1);
|
|
expect(permissionCodes).toHaveLength(1);
|
|
expect(networkCodes).toHaveLength(1);
|
|
});
|
|
|
|
it('should support error code with complex details', () => {
|
|
interface ComplexErrorDetails {
|
|
error: {
|
|
code: string;
|
|
message: string;
|
|
stack?: string;
|
|
};
|
|
context: {
|
|
service: string;
|
|
operation: string;
|
|
timestamp: string;
|
|
};
|
|
metadata: {
|
|
retryCount: number;
|
|
timeout: number;
|
|
};
|
|
}
|
|
|
|
const complexError: ApplicationErrorCode<'SYSTEM_ERROR', ComplexErrorDetails> = {
|
|
code: 'SYSTEM_ERROR',
|
|
details: {
|
|
error: {
|
|
code: 'E001',
|
|
message: 'System failure',
|
|
stack: 'Error stack trace...'
|
|
},
|
|
context: {
|
|
service: 'payment-service',
|
|
operation: 'processPayment',
|
|
timestamp: new Date().toISOString()
|
|
},
|
|
metadata: {
|
|
retryCount: 3,
|
|
timeout: 5000
|
|
}
|
|
}
|
|
};
|
|
|
|
expect(complexError.code).toBe('SYSTEM_ERROR');
|
|
expect(complexError.details).toBeDefined();
|
|
expect(complexError.details?.error.code).toBe('E001');
|
|
expect(complexError.details?.context.service).toBe('payment-service');
|
|
expect(complexError.details?.metadata.retryCount).toBe(3);
|
|
});
|
|
});
|
|
});
|