diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.test.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.test.ts
new file mode 100644
index 0000000000000..97aee1d943bc3
--- /dev/null
+++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.test.ts
@@ -0,0 +1,55 @@
+import { RateLimitError } from 'openai';
+import { OpenAIError } from 'openai/error';
+
+import { openAiFailedAttemptHandler, getCustomErrorMessage, isOpenAiError } from './error-handling';
+
+describe('error-handling', () => {
+ describe('getCustomErrorMessage', () => {
+ it('should return the correct custom error message for known error codes', () => {
+ expect(getCustomErrorMessage('insufficient_quota')).toBe(
+ 'Insufficient quota detected. Learn more about resolving this issue',
+ );
+ expect(getCustomErrorMessage('rate_limit_exceeded')).toBe('OpenAI: Rate limit reached');
+ });
+
+ it('should return undefined for unknown error codes', () => {
+ expect(getCustomErrorMessage('unknown_error_code')).toBeUndefined();
+ });
+ });
+
+ describe('isOpenAiError', () => {
+ it('should return true if the error is an instance of OpenAIError', () => {
+ const error = new OpenAIError('Test error');
+ expect(isOpenAiError(error)).toBe(true);
+ });
+
+ it('should return false if the error is not an instance of OpenAIError', () => {
+ const error = new Error('Test error');
+ expect(isOpenAiError(error)).toBe(false);
+ });
+ });
+
+ describe('openAiFailedAttemptHandler', () => {
+ it('should handle RateLimitError and modify the error message', () => {
+ const error = new RateLimitError(
+ 429,
+ { code: 'rate_limit_exceeded' },
+ 'Rate limit exceeded',
+ {},
+ );
+
+ try {
+ openAiFailedAttemptHandler(error);
+ } catch (e) {
+ expect(e).toBe(error);
+ expect(e.message).toBe('OpenAI: Rate limit reached');
+ }
+ });
+
+ it('should throw the error if it is not a RateLimitError', () => {
+ const error = new Error('Test error');
+
+ expect(() => openAiFailedAttemptHandler(error)).not.toThrow();
+ });
+ });
+});
diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.ts
index 5cea5eaf5109b..4fbb140def424 100644
--- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.ts
+++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/helpers/error-handling.ts
@@ -1,8 +1,9 @@
-import { OpenAIError } from 'openai/error';
import { RateLimitError } from 'openai';
+import { OpenAIError } from 'openai/error';
const errorMap: Record = {
- insufficient_quota: 'OpenAI: Insufficient quota',
+ insufficient_quota:
+ 'Insufficient quota detected. Learn more about resolving this issue',
rate_limit_exceeded: 'OpenAI: Rate limit reached',
};
@@ -23,7 +24,10 @@ export const openAiFailedAttemptHandler = (error: any) => {
const customErrorMessage = getCustomErrorMessage(errorCode);
if (customErrorMessage) {
- error.message = customErrorMessage;
+ if (error.error) {
+ (error.error as { message: string }).message = customErrorMessage;
+ error.message = customErrorMessage;
+ }
}
}
diff --git a/packages/editor-ui/src/components/NodeExecutionErrorMessage.test.ts b/packages/editor-ui/src/components/NodeExecutionErrorMessage.test.ts
new file mode 100644
index 0000000000000..3bc51a18e9a34
--- /dev/null
+++ b/packages/editor-ui/src/components/NodeExecutionErrorMessage.test.ts
@@ -0,0 +1,91 @@
+import { describe, it, expect } from 'vitest';
+import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue';
+import { createComponentRenderer } from '@/__tests__/render';
+
+const renderComponent = createComponentRenderer(NodeExecutionErrorMessage);
+
+describe('NodeExecutionErrorMessage', () => {
+ it('renders the component', () => {
+ const { getByTestId } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage: 'An error occurred',
+ },
+ });
+ expect(getByTestId('sanitized-error-message')).toHaveTextContent('An error occurred');
+ });
+
+ it('renders sanitized HTML in error message', () => {
+ const { getByTestId } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage:
+ 'Insufficient quota detected. Learn more',
+ },
+ });
+ expect(getByTestId('sanitized-error-message')).toContainHTML(
+ 'Insufficient quota detected. Learn more',
+ );
+ });
+
+ it('renders the link with the correct text', () => {
+ const { getByText } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage: 'An error occurred',
+ },
+ });
+ expect(getByText('Open node')).toBeTruthy();
+ });
+
+ it('renders the link with the correct data attributes', () => {
+ const { getByText } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage: 'An error occurred',
+ },
+ });
+ const link = getByText('Open node');
+ expect(link.getAttribute('data-action')).toBe('openNodeDetail');
+ expect(link.getAttribute('data-action-parameter-node')).toBe('Test Node');
+ });
+
+ it('does not render error message when it is not provided', () => {
+ const { queryByText } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ },
+ });
+ expect(queryByText('An error occurred')).not.toBeInTheDocument();
+ });
+
+ it('sanitizes malicious script in error message', () => {
+ const { getByTestId } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage: '',
+ },
+ });
+ expect(getByTestId('sanitized-error-message')).toContainHTML('');
+ });
+
+ it('sanitizes malicious script in error message with nested tags', () => {
+ const { getByTestId } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage: '',
+ },
+ });
+ expect(getByTestId('sanitized-error-message')).toContainHTML('');
+ });
+
+ it('sanitizes malicious script in error message with script tag', () => {
+ const { container } = renderComponent({
+ props: {
+ nodeName: 'Test Node',
+ errorMessage: '',
+ },
+ });
+ expect(container.querySelector('script')).not.toBeInTheDocument();
+ });
+});
diff --git a/packages/editor-ui/src/components/NodeExecutionErrorMessage.vue b/packages/editor-ui/src/components/NodeExecutionErrorMessage.vue
index 5786495df4eb3..26db3153ec7e2 100644
--- a/packages/editor-ui/src/components/NodeExecutionErrorMessage.vue
+++ b/packages/editor-ui/src/components/NodeExecutionErrorMessage.vue
@@ -1,5 +1,6 @@