UNPKG

@lobehub/chat

Version:

Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.

370 lines (297 loc) • 14.2 kB
import { describe, expect, it } from 'vitest'; import { AgentRuntimeErrorType } from '../types/error'; import { cleanComfyUIErrorMessage, parseComfyUIErrorMessage } from './comfyuiErrorParser'; describe('comfyuiErrorParser', () => { describe('cleanComfyUIErrorMessage', () => { it('should remove leading asterisks and spaces', () => { const message = '* Error message'; expect(cleanComfyUIErrorMessage(message)).toBe('Error message'); // Test multiple asterisks const multiAsterisk = '* * * Error message'; expect(cleanComfyUIErrorMessage(multiAsterisk)).toBe('* * Error message'); }); it('should convert escaped newlines', () => { const message = 'Line 1\\nLine 2'; expect(cleanComfyUIErrorMessage(message)).toBe('Line 1 Line 2'); }); it('should replace multiple newlines with single space', () => { const message = 'Line 1\n\n\nLine 2'; expect(cleanComfyUIErrorMessage(message)).toBe('Line 1 Line 2'); }); it('should trim leading and trailing spaces', () => { const message = ' Error message '; expect(cleanComfyUIErrorMessage(message)).toBe('Error message'); }); }); describe('parseComfyUIErrorMessage', () => { describe('HTTP status code errors', () => { it('should identify 401 as InvalidProviderAPIKey', () => { const error = { message: 'Unauthorized', status: 401 }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.InvalidProviderAPIKey); }); it('should identify 403 as PermissionDenied', () => { const error = { message: 'Forbidden', status: 403 }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.PermissionDenied); }); it('should identify 404 as InvalidProviderAPIKey', () => { const error = { message: 'Not Found', status: 404 }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.InvalidProviderAPIKey); }); it('should identify 500+ as ComfyUIServiceUnavailable', () => { const error = { message: 'Internal Server Error', status: 500 }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIServiceUnavailable); }); it('should identify HTTP status in message when status field missing', () => { const error = { message: 'Request failed with HTTP 401' }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.InvalidProviderAPIKey); }); }); describe('Network errors', () => { it('should return ComfyUIBizError for fetch failed (processed by server)', () => { const error = new Error('fetch failed'); const result = parseComfyUIErrorMessage(error); // Network error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('fetch failed'); }); it('should return ComfyUIBizError for ECONNREFUSED (processed by server)', () => { const error = { message: 'Connection ECONNREFUSED', code: 'ECONNREFUSED' }; const result = parseComfyUIErrorMessage(error); // Network error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Connection ECONNREFUSED'); }); it('should return ComfyUIBizError for WebSocket errors (processed by server)', () => { const error = { message: 'WebSocket connection failed', code: 'WS_CONNECTION_FAILED' }; const result = parseComfyUIErrorMessage(error); // Network error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('WebSocket connection failed'); }); }); describe('Model errors', () => { it('should return ComfyUIBizError for model not found (processed by server)', () => { const error = { message: 'Model not found: flux1-dev.safetensors' }; const result = parseComfyUIErrorMessage(error); // Model error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Model not found: flux1-dev.safetensors'); }); it('should return ComfyUIBizError for checkpoint not found (processed by server)', () => { const error = { message: 'Checkpoint not found' }; const result = parseComfyUIErrorMessage(error); // Model error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Checkpoint not found'); }); it('should return ComfyUIBizError for safetensors file errors (processed by server)', () => { const error = { message: 'Missing file: model.safetensors' }; const result = parseComfyUIErrorMessage(error); // Model error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Missing file: model.safetensors'); }); it('should preserve server-provided file info but return ComfyUIBizError', () => { const error = { message: 'Some error', missingFileName: 'flux1-dev.safetensors', missingFileType: 'model', }; const result = parseComfyUIErrorMessage(error); // File info is preserved but error type detection is server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.missingFileName).toBe('flux1-dev.safetensors'); expect(result.error.missingFileType).toBe('model'); }); }); describe('Workflow errors', () => { it('should return ComfyUIBizError for workflow validation errors (processed by server)', () => { const error = { message: 'Workflow validation failed' }; const result = parseComfyUIErrorMessage(error); // Workflow error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Workflow validation failed'); }); it('should return ComfyUIBizError for node execution errors (processed by server)', () => { const error = { message: 'Node execution failed', node_id: '5', node_type: 'KSampler', }; const result = parseComfyUIErrorMessage(error); // Workflow error detection moved to server-side, but node info is preserved expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Node execution failed'); expect(result.error.details).toEqual({ node_id: '5', node_type: 'KSampler', }); }); it('should return ComfyUIBizError for queue errors (processed by server)', () => { const error = { message: 'Queue processing error' }; const result = parseComfyUIErrorMessage(error); // Workflow error detection moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Queue processing error'); }); }); describe('SDK custom errors', () => { it('should identify SDK error classes', () => { const error = { name: 'ExecutionFailedError', message: 'Execution failed', }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); }); it('should identify SDK error messages', () => { const error = { message: 'SDK Error: Invalid configuration' }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); }); }); describe('JSON parsing errors', () => { it('should return ComfyUIBizError for SyntaxError (SyntaxError detection moved to server)', () => { const error = new SyntaxError('Unexpected token < in JSON at position 0'); const result = parseComfyUIErrorMessage(error); // SyntaxError detection and message enhancement moved to server-side expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('Unexpected token < in JSON at position 0'); expect(result.error.type).toBe('SyntaxError'); }); }); describe('Error information extraction', () => { it('should extract error info from string', () => { const error = 'Simple error message'; const result = parseComfyUIErrorMessage(error); expect(result.error.message).toBe('Simple error message'); }); it('should extract error info from Error object', () => { const error = new Error('Error message'); (error as any).code = 'ERROR_CODE'; (error as any).status = 500; const result = parseComfyUIErrorMessage(error); expect(result.error.message).toBe('Error message'); expect(result.error.code).toBe('ERROR_CODE'); expect(result.error.status).toBe(500); expect(result.error.type).toBe('Error'); }); it('should extract error info from structured object', () => { const error = { message: 'Error message', code: 'ERROR_CODE', status: 400, details: { foo: 'bar' }, node_id: '5', node_type: 'KSampler', }; const result = parseComfyUIErrorMessage(error); expect(result.error.message).toBe('Error message'); expect(result.error.code).toBe('ERROR_CODE'); expect(result.error.status).toBe(400); expect(result.error.details).toEqual({ foo: 'bar', node_id: '5', node_type: 'KSampler', }); }); it('should preserve server-generated file info and guidance', () => { const error = { message: 'Model file missing', missingFileName: 'flux1-dev.safetensors', missingFileType: 'model' as const, userGuidance: 'Please download the model from...', }; const result = parseComfyUIErrorMessage(error); expect(result.error.missingFileName).toBe('flux1-dev.safetensors'); expect(result.error.missingFileType).toBe('model'); expect(result.error.userGuidance).toBe('Please download the model from...'); }); it('should extract nested error info from various locations', () => { const error = { body: { error: { message: 'Nested error', missingFileName: 'ae.safetensors', userGuidance: 'Download VAE model', }, }, }; const result = parseComfyUIErrorMessage(error); expect(result.error.missingFileName).toBe('ae.safetensors'); expect(result.error.userGuidance).toBe('Download VAE model'); }); it('should handle cause field (SDK pattern)', () => { const error = new Error('Wrapper error'); (error as any).cause = { message: 'Actual error', code: 'ACTUAL_CODE', }; const result = parseComfyUIErrorMessage(error); expect(result.error.message).toBe('Actual error'); expect(result.error.code).toBe('ACTUAL_CODE'); // Type comes from the cause object's constructor name (plain object = "Object") expect(result.error.type).toBe('Object'); }); it('should extract message from various possible sources', () => { const error = { exception_message: 'ComfyUI exception', error: { message: 'Should not use this', }, }; const result = parseComfyUIErrorMessage(error); // exception_message has highest priority expect(result.error.message).toBe('ComfyUI exception'); }); }); describe('AgentRuntimeError handling', () => { it('should detect and return AgentRuntimeError as-is', () => { const agentRuntimeError = { error: { message: 'Model not found', missingFileName: 'flux1-dev.safetensors', missingFileType: 'model', userGuidance: 'Please download the model', }, errorType: AgentRuntimeErrorType.ModelNotFound, provider: 'comfyui', }; const result = parseComfyUIErrorMessage(agentRuntimeError); expect(result.errorType).toBe(AgentRuntimeErrorType.ModelNotFound); expect(result.error).toEqual(agentRuntimeError.error); }); it('should handle AgentRuntimeError with InvalidProviderAPIKey', () => { const agentRuntimeError = { error: { message: 'Authentication failed', status: 401, }, errorType: AgentRuntimeErrorType.InvalidProviderAPIKey, provider: 'comfyui', }; const result = parseComfyUIErrorMessage(agentRuntimeError); expect(result.errorType).toBe(AgentRuntimeErrorType.InvalidProviderAPIKey); expect(result.error.message).toBe('Authentication failed'); }); }); describe('Default error handling', () => { it('should handle unknown error types', () => { const error = { random: 'data' }; const result = parseComfyUIErrorMessage(error); expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toContain('object'); }); it('should handle null/undefined gracefully', () => { const result = parseComfyUIErrorMessage(null); expect(result.errorType).toBe(AgentRuntimeErrorType.ComfyUIBizError); expect(result.error.message).toBe('null'); }); }); }); });