mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(GraphQL Node): Throw error if GraphQL variables are not objects or strings (#11904)
This commit is contained in:
parent
3814f42ada
commit
85f30b27ae
|
@ -418,41 +418,50 @@ export class GraphQL implements INodeType {
|
||||||
|
|
||||||
const gqlQuery = this.getNodeParameter('query', itemIndex, '') as string;
|
const gqlQuery = this.getNodeParameter('query', itemIndex, '') as string;
|
||||||
if (requestMethod === 'GET') {
|
if (requestMethod === 'GET') {
|
||||||
if (!requestOptions.qs) {
|
requestOptions.qs = requestOptions.qs ?? {};
|
||||||
requestOptions.qs = {};
|
|
||||||
}
|
|
||||||
requestOptions.qs.query = gqlQuery;
|
requestOptions.qs.query = gqlQuery;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
if (requestFormat === 'json') {
|
if (requestFormat === 'json') {
|
||||||
const jsonBody = {
|
const variables = this.getNodeParameter('variables', itemIndex, {});
|
||||||
...requestOptions.body,
|
|
||||||
query: gqlQuery,
|
let parsedVariables;
|
||||||
variables: this.getNodeParameter('variables', itemIndex, {}) as object,
|
if (typeof variables === 'string') {
|
||||||
operationName: this.getNodeParameter('operationName', itemIndex) as string,
|
|
||||||
};
|
|
||||||
if (typeof jsonBody.variables === 'string') {
|
|
||||||
try {
|
try {
|
||||||
jsonBody.variables = JSON.parse(jsonBody.variables || '{}');
|
parsedVariables = JSON.parse(variables || '{}');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
'Using variables failed:\n' +
|
`Using variables failed:\n${variables}\n\nWith error message:\n${error}`,
|
||||||
(jsonBody.variables as string) +
|
|
||||||
'\n\nWith error message:\n' +
|
|
||||||
(error as string),
|
|
||||||
{ itemIndex },
|
{ itemIndex },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (typeof variables === 'object' && variables !== null) {
|
||||||
|
parsedVariables = variables;
|
||||||
|
} else {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`Using variables failed:\n${variables}\n\nGraphQL variables should be either an object or a string.`,
|
||||||
|
{ itemIndex },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsonBody = {
|
||||||
|
...requestOptions.body,
|
||||||
|
query: gqlQuery,
|
||||||
|
variables: parsedVariables,
|
||||||
|
operationName: this.getNodeParameter('operationName', itemIndex) as string,
|
||||||
|
};
|
||||||
|
|
||||||
if (jsonBody.operationName === '') {
|
if (jsonBody.operationName === '') {
|
||||||
jsonBody.operationName = null;
|
jsonBody.operationName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestOptions.json = true;
|
requestOptions.json = true;
|
||||||
requestOptions.body = jsonBody;
|
requestOptions.body = jsonBody;
|
||||||
} else {
|
} else {
|
||||||
requestOptions.body = gqlQuery;
|
requestOptions.body = gqlQuery;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
// Now that the options are all set make the actual http request
|
// Now that the options are all set make the actual http request
|
||||||
|
@ -509,22 +518,19 @@ export class GraphQL implements INodeType {
|
||||||
throw new NodeApiError(this.getNode(), response.errors as JsonObject, { message });
|
throw new NodeApiError(this.getNode(), response.errors as JsonObject, { message });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (!this.continueOnFail()) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const errorData = this.helpers.returnJsonArray({
|
const errorData = this.helpers.returnJsonArray({
|
||||||
$error: error,
|
error: error.message,
|
||||||
json: this.getInputData(itemIndex),
|
|
||||||
itemIndex,
|
|
||||||
});
|
});
|
||||||
const exectionErrorWithMetaData = this.helpers.constructExecutionMetaData(errorData, {
|
const exectionErrorWithMetaData = this.helpers.constructExecutionMetaData(errorData, {
|
||||||
itemData: { item: itemIndex },
|
itemData: { item: itemIndex },
|
||||||
});
|
});
|
||||||
returnItems.push(...exectionErrorWithMetaData);
|
returnItems.push(...exectionErrorWithMetaData);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [returnItems];
|
return [returnItems];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,83 @@
|
||||||
import type { WorkflowTestData } from '@test/nodes/types';
|
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||||
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
import nock from 'nock';
|
||||||
import * as Helpers from '@test/nodes/Helpers';
|
|
||||||
|
import {
|
||||||
|
equalityTest,
|
||||||
|
getWorkflowFilenames,
|
||||||
|
initBinaryDataService,
|
||||||
|
setup,
|
||||||
|
workflowToTests,
|
||||||
|
} from '@test/nodes/Helpers';
|
||||||
|
|
||||||
describe('GraphQL Node', () => {
|
describe('GraphQL Node', () => {
|
||||||
const mockResponse = {
|
const workflows = getWorkflowFilenames(__dirname);
|
||||||
|
const workflowTests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
const baseUrl = 'https://api.n8n.io/';
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await initBinaryDataService();
|
||||||
|
nock.disableNetConnect();
|
||||||
|
|
||||||
|
nock(baseUrl)
|
||||||
|
.matchHeader('accept', 'application/json')
|
||||||
|
.matchHeader('content-type', 'application/json')
|
||||||
|
.matchHeader('user-agent', 'axios/1.7.4')
|
||||||
|
.matchHeader('content-length', '263')
|
||||||
|
.matchHeader('accept-encoding', 'gzip, compress, deflate, br')
|
||||||
|
.post(
|
||||||
|
'/graphql',
|
||||||
|
'{"query":"query {\\n nodes(pagination: { limit: 1 }) {\\n data {\\n id\\n attributes {\\n name\\n displayName\\n description\\n group\\n codex\\n createdAt\\n }\\n }\\n }\\n}","variables":{},"operationName":null}',
|
||||||
|
)
|
||||||
|
.reply(200, {
|
||||||
data: {
|
data: {
|
||||||
nodes: {},
|
nodes: {
|
||||||
},
|
data: [
|
||||||
};
|
{
|
||||||
|
id: '1',
|
||||||
|
attributes: {
|
||||||
|
name: 'n8n-nodes-base.activeCampaign',
|
||||||
|
displayName: 'ActiveCampaign',
|
||||||
|
description: 'Create and edit data in ActiveCampaign',
|
||||||
|
group: '["transform"]',
|
||||||
|
|
||||||
const tests: WorkflowTestData[] = [
|
codex: {
|
||||||
|
data: {
|
||||||
|
details:
|
||||||
|
'ActiveCampaign is a cloud software platform that allows customer experience automation, which combines email marketing, marketing automation, sales automation, and CRM categories. Use this node when you want to interact with your ActiveCampaign account.',
|
||||||
|
resources: {
|
||||||
|
primaryDocumentation: [
|
||||||
{
|
{
|
||||||
description: 'should run Request Format JSON',
|
url: 'https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.activecampaign/',
|
||||||
input: {
|
|
||||||
workflowData: Helpers.readJsonFileSync('nodes/GraphQL/test/workflow.json'),
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
nodeExecutionOrder: ['Start'],
|
|
||||||
nodeData: {
|
|
||||||
'Fetch Request Format JSON': [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
json: mockResponse,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
credentialDocumentation: [
|
||||||
|
{
|
||||||
|
url: 'https://docs.n8n.io/integrations/builtin/credentials/activeCampaign/',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
categories: ['Marketing'],
|
||||||
|
nodeVersion: '1.0',
|
||||||
|
codexVersion: '1.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: '2019-08-30T22:54:39.934Z',
|
||||||
},
|
},
|
||||||
nock: {
|
|
||||||
baseUrl: 'https://api.n8n.io',
|
|
||||||
mocks: [
|
|
||||||
{
|
|
||||||
method: 'post',
|
|
||||||
path: '/graphql',
|
|
||||||
statusCode: 200,
|
|
||||||
responseBody: mockResponse,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
|
||||||
|
|
||||||
const nodeTypes = Helpers.setup(tests);
|
|
||||||
|
|
||||||
test.each(tests)('$description', async (testData) => {
|
|
||||||
const { result } = await executeWorkflow(testData, nodeTypes);
|
|
||||||
const resultNodeData = Helpers.getResultNodeData(result, testData);
|
|
||||||
resultNodeData.forEach(({ nodeName, resultData }) =>
|
|
||||||
expect(resultData).toEqual(testData.output.nodeData[nodeName]),
|
|
||||||
);
|
|
||||||
expect(result.finished).toEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(workflowTests);
|
||||||
|
|
||||||
|
for (const workflow of workflowTests) {
|
||||||
|
test(workflow.description, async () => await equalityTest(workflow, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"templateId": "216",
|
||||||
|
"instanceId": "ee90fdf8d57662f949e6c691dc07fa0fd2f66e1eee28ed82ef06658223e67255"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"endpoint": "https://graphql-teas-endpoint.netlify.app/",
|
||||||
|
"requestFormat": "json",
|
||||||
|
"query": "query getAllTeas($name: String) {\n teas(name: $name) {\n name,\n id\n }\n}",
|
||||||
|
"variables": "={{ 1 }}"
|
||||||
|
},
|
||||||
|
"id": "7aece03f-e0d9-4f49-832c-fc6465613ca7",
|
||||||
|
"name": "Test: Errors on unsuccessful Expression validation",
|
||||||
|
"type": "n8n-nodes-base.graphql",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [660, 200],
|
||||||
|
"onError": "continueRegularOutput"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {},
|
||||||
|
"pinData": {
|
||||||
|
"Test: Errors on unsuccessful Expression validation": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"error": "Using variables failed:\n1\n\nGraphQL variables should be either an object or a string."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"meta": {
|
"meta": {
|
||||||
"templateCredsSetupCompleted": true,
|
"templateId": "216",
|
||||||
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
|
"instanceId": "ee90fdf8d57662f949e6c691dc07fa0fd2f66e1eee28ed82ef06658223e67255"
|
||||||
},
|
},
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"parameters": {},
|
"parameters": {},
|
||||||
"id": "fb826323-2e48-4f11-bb0e-e12de32e22ee",
|
"id": "5e2ef15b-2c6c-412f-a9da-515b5211386e",
|
||||||
"name": "When clicking ‘Test workflow’",
|
"name": "When clicking ‘Test workflow’",
|
||||||
"type": "n8n-nodes-base.manualTrigger",
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
"position": [180, 160]
|
"position": [420, 100]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
@ -21,8 +21,8 @@
|
||||||
"name": "Fetch Request Format JSON",
|
"name": "Fetch Request Format JSON",
|
||||||
"type": "n8n-nodes-base.graphql",
|
"type": "n8n-nodes-base.graphql",
|
||||||
"typeVersion": 1,
|
"typeVersion": 1,
|
||||||
"position": [420, 160],
|
"position": [700, 140],
|
||||||
"id": "7f8ceaf4-b82f-48d5-be0b-9fe3bfb35ee4"
|
"id": "e1c750a0-8d6c-4e81-8111-3218e1e6e69f"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"connections": {
|
"connections": {
|
||||||
|
@ -38,5 +38,48 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pinData": {}
|
"pinData": {
|
||||||
|
"Fetch Request Format JSON": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"data": {
|
||||||
|
"nodes": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"attributes": {
|
||||||
|
"name": "n8n-nodes-base.activeCampaign",
|
||||||
|
"displayName": "ActiveCampaign",
|
||||||
|
"description": "Create and edit data in ActiveCampaign",
|
||||||
|
"group": "[\"transform\"]",
|
||||||
|
"codex": {
|
||||||
|
"data": {
|
||||||
|
"details": "ActiveCampaign is a cloud software platform that allows customer experience automation, which combines email marketing, marketing automation, sales automation, and CRM categories. Use this node when you want to interact with your ActiveCampaign account.",
|
||||||
|
"resources": {
|
||||||
|
"primaryDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.activecampaign/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"credentialDocumentation": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.n8n.io/integrations/builtin/credentials/activeCampaign/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"categories": ["Marketing"],
|
||||||
|
"nodeVersion": "1.0",
|
||||||
|
"codexVersion": "1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"createdAt": "2019-08-30T22:54:39.934Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue