fix(GraphQL Node): Improve error response handling (#28209)

This commit is contained in:
RomanDavydchuk 2026-04-14 13:12:48 +03:00 committed by GitHub
parent 98b833a07d
commit 357fb7210a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 86 additions and 3 deletions

View file

@ -552,9 +552,15 @@ export class GraphQL implements INodeType {
}
// throw from response object.errors[]
if (typeof response === 'object' && response.errors) {
const message =
response.errors?.map((error: IDataObject) => error.message).join(', ') ||
'Unexpected error';
let message = 'Unexpected error';
if (Array.isArray(response.errors)) {
message = (response.errors as IDataObject[])
.map((error) => error.message ?? error)
.join(', ');
} else if (typeof response.errors === 'string') {
message = response.errors;
}
throw new NodeApiError(this.getNode(), response.errors as JsonObject, { message });
}
} catch (error) {

View file

@ -1,6 +1,12 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import { mockDeep } from 'jest-mock-extended';
import get from 'lodash/get';
import { constructExecutionMetaData, returnJsonArray } from 'n8n-core';
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
import nock from 'nock';
import { GraphQL } from '../GraphQL.node';
describe('GraphQL Node', () => {
describe('valid request', () => {
const baseUrl = 'https://api.n8n.io/';
@ -110,4 +116,75 @@ describe('GraphQL Node', () => {
credentials,
});
});
describe('error response', () => {
const createMockExecuteFunctions = (parameters: IDataObject) => {
const mockExecuteFunctions = mockDeep<IExecuteFunctions>();
mockExecuteFunctions.getNodeParameter.mockImplementation((parameter, _idx, fallbackValue) => {
return get(parameters, parameter) ?? fallbackValue;
});
mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]);
mockExecuteFunctions.helpers.returnJsonArray.mockImplementation(returnJsonArray);
mockExecuteFunctions.helpers.constructExecutionMetaData.mockImplementation(
constructExecutionMetaData,
);
return mockExecuteFunctions;
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should format error response if response.errors is an array with objects', async () => {
const mockExecuteFunctions = createMockExecuteFunctions({
query: 'query { foo }',
});
mockExecuteFunctions.helpers.request.mockResolvedValue({
errors: [{ message: 'Bad format 1' }, { message: 'Bad format 2' }],
});
const node = new GraphQL();
await expect(node.execute.call(mockExecuteFunctions)).rejects.toThrow(
'Bad format 1, Bad format 2',
);
});
it('should format error response if response.errors is an array with strings', async () => {
const mockExecuteFunctions = createMockExecuteFunctions({
query: 'query { foo }',
});
mockExecuteFunctions.helpers.request.mockResolvedValue({
errors: ['Bad format 1', 'Bad format 2'],
});
const node = new GraphQL();
await expect(node.execute.call(mockExecuteFunctions)).rejects.toThrow(
'Bad format 1, Bad format 2',
);
});
it('should format error response if response.errors is a string', async () => {
const mockExecuteFunctions = createMockExecuteFunctions({
query: 'query { foo }',
});
mockExecuteFunctions.helpers.request.mockResolvedValue({
errors: 'Bad format',
});
const node = new GraphQL();
await expect(node.execute.call(mockExecuteFunctions)).rejects.toThrow('Bad format');
});
it('should fallback to unexpected error if response.errors is not an array or string', async () => {
const mockExecuteFunctions = createMockExecuteFunctions({
query: 'query { foo }',
});
mockExecuteFunctions.helpers.request.mockResolvedValue({
errors: 123,
});
const node = new GraphQL();
await expect(node.execute.call(mockExecuteFunctions)).rejects.toThrow('Unexpected error');
});
});
});