mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 14:32:04 +00:00
- Add comprehensive tests for ValidationServiceError (25 tests) - Add tests for NodeRepository operations methods (23 tests) - Add comprehensive tests for ResourceSimilarityService (66 tests) - Add comprehensive tests for OperationSimilarityService (58 tests) - Add integration tests for EnhancedConfigValidator (15 tests) - Fix EnhancedConfigValidator to handle errors gracefully - Add suggestions to both error objects and result.suggestions array - Improve overall test coverage from 69.76% towards 80%+ target 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
300 lines
11 KiB
TypeScript
300 lines
11 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { ValidationServiceError } from '@/errors/validation-service-error';
|
|
|
|
describe('ValidationServiceError', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create error with basic message', () => {
|
|
const error = new ValidationServiceError('Test error message');
|
|
|
|
expect(error.name).toBe('ValidationServiceError');
|
|
expect(error.message).toBe('Test error message');
|
|
expect(error.nodeType).toBeUndefined();
|
|
expect(error.property).toBeUndefined();
|
|
expect(error.cause).toBeUndefined();
|
|
});
|
|
|
|
it('should create error with all parameters', () => {
|
|
const cause = new Error('Original error');
|
|
const error = new ValidationServiceError(
|
|
'Validation failed',
|
|
'nodes-base.slack',
|
|
'channel',
|
|
cause
|
|
);
|
|
|
|
expect(error.name).toBe('ValidationServiceError');
|
|
expect(error.message).toBe('Validation failed');
|
|
expect(error.nodeType).toBe('nodes-base.slack');
|
|
expect(error.property).toBe('channel');
|
|
expect(error.cause).toBe(cause);
|
|
});
|
|
|
|
it('should maintain proper inheritance from Error', () => {
|
|
const error = new ValidationServiceError('Test message');
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
expect(error).toBeInstanceOf(ValidationServiceError);
|
|
});
|
|
|
|
it('should capture stack trace when Error.captureStackTrace is available', () => {
|
|
const originalCaptureStackTrace = Error.captureStackTrace;
|
|
const mockCaptureStackTrace = vi.fn();
|
|
Error.captureStackTrace = mockCaptureStackTrace;
|
|
|
|
const error = new ValidationServiceError('Test message');
|
|
|
|
expect(mockCaptureStackTrace).toHaveBeenCalledWith(error, ValidationServiceError);
|
|
|
|
// Restore original
|
|
Error.captureStackTrace = originalCaptureStackTrace;
|
|
});
|
|
|
|
it('should handle missing Error.captureStackTrace gracefully', () => {
|
|
const originalCaptureStackTrace = Error.captureStackTrace;
|
|
// @ts-ignore - testing edge case
|
|
delete Error.captureStackTrace;
|
|
|
|
expect(() => {
|
|
new ValidationServiceError('Test message');
|
|
}).not.toThrow();
|
|
|
|
// Restore original
|
|
Error.captureStackTrace = originalCaptureStackTrace;
|
|
});
|
|
});
|
|
|
|
describe('jsonParseError factory', () => {
|
|
it('should create error for JSON parsing failure', () => {
|
|
const cause = new SyntaxError('Unexpected token');
|
|
const error = ValidationServiceError.jsonParseError('nodes-base.slack', cause);
|
|
|
|
expect(error.name).toBe('ValidationServiceError');
|
|
expect(error.message).toBe('Failed to parse JSON data for node nodes-base.slack');
|
|
expect(error.nodeType).toBe('nodes-base.slack');
|
|
expect(error.property).toBeUndefined();
|
|
expect(error.cause).toBe(cause);
|
|
});
|
|
|
|
it('should handle different error types as cause', () => {
|
|
const cause = new TypeError('Cannot read property');
|
|
const error = ValidationServiceError.jsonParseError('nodes-base.webhook', cause);
|
|
|
|
expect(error.cause).toBe(cause);
|
|
expect(error.message).toContain('nodes-base.webhook');
|
|
});
|
|
|
|
it('should work with Error instances', () => {
|
|
const cause = new Error('Generic parsing error');
|
|
const error = ValidationServiceError.jsonParseError('nodes-base.httpRequest', cause);
|
|
|
|
expect(error.cause).toBe(cause);
|
|
expect(error.nodeType).toBe('nodes-base.httpRequest');
|
|
});
|
|
});
|
|
|
|
describe('nodeNotFound factory', () => {
|
|
it('should create error for missing node type', () => {
|
|
const error = ValidationServiceError.nodeNotFound('nodes-base.nonexistent');
|
|
|
|
expect(error.name).toBe('ValidationServiceError');
|
|
expect(error.message).toBe('Node type nodes-base.nonexistent not found in repository');
|
|
expect(error.nodeType).toBe('nodes-base.nonexistent');
|
|
expect(error.property).toBeUndefined();
|
|
expect(error.cause).toBeUndefined();
|
|
});
|
|
|
|
it('should work with various node type formats', () => {
|
|
const nodeTypes = [
|
|
'nodes-base.slack',
|
|
'@n8n/n8n-nodes-langchain.chatOpenAI',
|
|
'custom-node',
|
|
''
|
|
];
|
|
|
|
nodeTypes.forEach(nodeType => {
|
|
const error = ValidationServiceError.nodeNotFound(nodeType);
|
|
expect(error.nodeType).toBe(nodeType);
|
|
expect(error.message).toBe(`Node type ${nodeType} not found in repository`);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('dataExtractionError factory', () => {
|
|
it('should create error for data extraction failure with cause', () => {
|
|
const cause = new Error('Database connection failed');
|
|
const error = ValidationServiceError.dataExtractionError(
|
|
'nodes-base.postgres',
|
|
'operations',
|
|
cause
|
|
);
|
|
|
|
expect(error.name).toBe('ValidationServiceError');
|
|
expect(error.message).toBe('Failed to extract operations for node nodes-base.postgres');
|
|
expect(error.nodeType).toBe('nodes-base.postgres');
|
|
expect(error.property).toBe('operations');
|
|
expect(error.cause).toBe(cause);
|
|
});
|
|
|
|
it('should create error for data extraction failure without cause', () => {
|
|
const error = ValidationServiceError.dataExtractionError(
|
|
'nodes-base.googleSheets',
|
|
'resources'
|
|
);
|
|
|
|
expect(error.name).toBe('ValidationServiceError');
|
|
expect(error.message).toBe('Failed to extract resources for node nodes-base.googleSheets');
|
|
expect(error.nodeType).toBe('nodes-base.googleSheets');
|
|
expect(error.property).toBe('resources');
|
|
expect(error.cause).toBeUndefined();
|
|
});
|
|
|
|
it('should handle various data types', () => {
|
|
const dataTypes = ['operations', 'resources', 'properties', 'credentials', 'schema'];
|
|
|
|
dataTypes.forEach(dataType => {
|
|
const error = ValidationServiceError.dataExtractionError(
|
|
'nodes-base.test',
|
|
dataType
|
|
);
|
|
expect(error.property).toBe(dataType);
|
|
expect(error.message).toBe(`Failed to extract ${dataType} for node nodes-base.test`);
|
|
});
|
|
});
|
|
|
|
it('should handle empty strings and special characters', () => {
|
|
const error = ValidationServiceError.dataExtractionError(
|
|
'nodes-base.test-node',
|
|
'special/property:name'
|
|
);
|
|
|
|
expect(error.property).toBe('special/property:name');
|
|
expect(error.message).toBe('Failed to extract special/property:name for node nodes-base.test-node');
|
|
});
|
|
});
|
|
|
|
describe('error properties and serialization', () => {
|
|
it('should maintain all properties when stringified', () => {
|
|
const cause = new Error('Root cause');
|
|
const error = ValidationServiceError.dataExtractionError(
|
|
'nodes-base.mysql',
|
|
'tables',
|
|
cause
|
|
);
|
|
|
|
// JSON.stringify doesn't include message by default for Error objects
|
|
const serialized = {
|
|
name: error.name,
|
|
message: error.message,
|
|
nodeType: error.nodeType,
|
|
property: error.property
|
|
};
|
|
|
|
expect(serialized.name).toBe('ValidationServiceError');
|
|
expect(serialized.message).toBe('Failed to extract tables for node nodes-base.mysql');
|
|
expect(serialized.nodeType).toBe('nodes-base.mysql');
|
|
expect(serialized.property).toBe('tables');
|
|
});
|
|
|
|
it('should work with toString method', () => {
|
|
const error = ValidationServiceError.nodeNotFound('nodes-base.missing');
|
|
const string = error.toString();
|
|
|
|
expect(string).toBe('ValidationServiceError: Node type nodes-base.missing not found in repository');
|
|
});
|
|
|
|
it('should preserve stack trace', () => {
|
|
const error = new ValidationServiceError('Test error');
|
|
expect(error.stack).toBeDefined();
|
|
expect(error.stack).toContain('ValidationServiceError');
|
|
});
|
|
});
|
|
|
|
describe('error chaining and nested causes', () => {
|
|
it('should handle nested error causes', () => {
|
|
const rootCause = new Error('Database unavailable');
|
|
const intermediateCause = new ValidationServiceError('Connection failed', 'nodes-base.db', undefined, rootCause);
|
|
const finalError = ValidationServiceError.jsonParseError('nodes-base.slack', intermediateCause);
|
|
|
|
expect(finalError.cause).toBe(intermediateCause);
|
|
expect((finalError.cause as ValidationServiceError).cause).toBe(rootCause);
|
|
});
|
|
|
|
it('should work with different error types in chain', () => {
|
|
const syntaxError = new SyntaxError('Invalid JSON');
|
|
const typeError = new TypeError('Property access failed');
|
|
const validationError = ValidationServiceError.dataExtractionError('nodes-base.test', 'props', syntaxError);
|
|
const finalError = ValidationServiceError.jsonParseError('nodes-base.final', typeError);
|
|
|
|
expect(validationError.cause).toBe(syntaxError);
|
|
expect(finalError.cause).toBe(typeError);
|
|
});
|
|
});
|
|
|
|
describe('edge cases and boundary conditions', () => {
|
|
it('should handle undefined and null values gracefully', () => {
|
|
// @ts-ignore - testing edge case
|
|
const error1 = new ValidationServiceError(undefined);
|
|
// @ts-ignore - testing edge case
|
|
const error2 = new ValidationServiceError(null);
|
|
|
|
// Test that constructor handles these values without throwing
|
|
expect(error1).toBeInstanceOf(ValidationServiceError);
|
|
expect(error2).toBeInstanceOf(ValidationServiceError);
|
|
expect(error1.name).toBe('ValidationServiceError');
|
|
expect(error2.name).toBe('ValidationServiceError');
|
|
});
|
|
|
|
it('should handle very long messages', () => {
|
|
const longMessage = 'a'.repeat(10000);
|
|
const error = new ValidationServiceError(longMessage);
|
|
|
|
expect(error.message).toBe(longMessage);
|
|
expect(error.message.length).toBe(10000);
|
|
});
|
|
|
|
it('should handle special characters in node types', () => {
|
|
const nodeType = 'nodes-base.test-node@1.0.0/special:version';
|
|
const error = ValidationServiceError.nodeNotFound(nodeType);
|
|
|
|
expect(error.nodeType).toBe(nodeType);
|
|
expect(error.message).toContain(nodeType);
|
|
});
|
|
|
|
it('should handle circular references in cause chain safely', () => {
|
|
const error1 = new ValidationServiceError('Error 1');
|
|
const error2 = new ValidationServiceError('Error 2', 'test', 'prop', error1);
|
|
|
|
// Don't actually create circular reference as it would break JSON.stringify
|
|
// Just verify the structure is set up correctly
|
|
expect(error2.cause).toBe(error1);
|
|
expect(error1.cause).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('factory method edge cases', () => {
|
|
it('should handle empty strings in factory methods', () => {
|
|
const jsonError = ValidationServiceError.jsonParseError('', new Error(''));
|
|
const notFoundError = ValidationServiceError.nodeNotFound('');
|
|
const extractionError = ValidationServiceError.dataExtractionError('', '');
|
|
|
|
expect(jsonError.nodeType).toBe('');
|
|
expect(notFoundError.nodeType).toBe('');
|
|
expect(extractionError.nodeType).toBe('');
|
|
expect(extractionError.property).toBe('');
|
|
});
|
|
|
|
it('should handle null-like values in cause parameter', () => {
|
|
// @ts-ignore - testing edge case
|
|
const error1 = ValidationServiceError.jsonParseError('test', null);
|
|
// @ts-ignore - testing edge case
|
|
const error2 = ValidationServiceError.dataExtractionError('test', 'prop', undefined);
|
|
|
|
expect(error1.cause).toBe(null);
|
|
expect(error2.cause).toBeUndefined();
|
|
});
|
|
});
|
|
}); |