pull:初次提交
This commit is contained in:
30
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/GraphQL.node.json
Executable file
30
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/GraphQL.node.json
Executable file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.graphql",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage", "Development"],
|
||||
"resources": {
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.graphql/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "What are APIs and how to use them with no code",
|
||||
"icon": " 🪢",
|
||||
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
|
||||
},
|
||||
{
|
||||
"label": "How to automatically give kudos to contributors with GitHub, Slack, and n8n",
|
||||
"icon": "👏",
|
||||
"url": "https://n8n.io/blog/how-to-automatically-give-kudos-to-contributors-with-github-slack-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How Goomer automated their operations with over 200 n8n workflows",
|
||||
"icon": "🛵",
|
||||
"url": "https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
576
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts
Executable file
576
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/GraphQL.node.ts
Executable file
@@ -0,0 +1,576 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
IRequestOptionsSimplified,
|
||||
IRequestOptions,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeConnectionTypes, NodeOperationError, jsonParse } from 'n8n-workflow';
|
||||
|
||||
export class GraphQL implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'GraphQL',
|
||||
name: 'graphql',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
|
||||
icon: 'file:graphql.png',
|
||||
group: ['input'],
|
||||
version: [1, 1.1],
|
||||
description: 'Makes a GraphQL request and returns the received data',
|
||||
defaults: {
|
||||
name: 'GraphQL',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'httpBasicAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['basicAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpCustomAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['customAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpDigestAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['digestAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpHeaderAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['headerAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'httpQueryAuth',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['queryAuth'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'oAuth1Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'oAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Basic Auth',
|
||||
value: 'basicAuth',
|
||||
},
|
||||
{
|
||||
name: 'Custom Auth',
|
||||
value: 'customAuth',
|
||||
},
|
||||
{
|
||||
name: 'Digest Auth',
|
||||
value: 'digestAuth',
|
||||
},
|
||||
{
|
||||
name: 'Header Auth',
|
||||
value: 'headerAuth',
|
||||
},
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'OAuth1',
|
||||
value: 'oAuth1',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
{
|
||||
name: 'Query Auth',
|
||||
value: 'queryAuth',
|
||||
},
|
||||
],
|
||||
default: 'none',
|
||||
description: 'The way to authenticate',
|
||||
},
|
||||
{
|
||||
displayName: 'HTTP Request Method',
|
||||
name: 'requestMethod',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'GET',
|
||||
value: 'GET',
|
||||
},
|
||||
{
|
||||
name: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
default: 'POST',
|
||||
description: 'The underlying HTTP request method to use',
|
||||
},
|
||||
{
|
||||
displayName: 'Endpoint',
|
||||
name: 'endpoint',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'http://example.com/graphql',
|
||||
description: 'The GraphQL endpoint',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Ignore SSL Issues (Insecure)',
|
||||
name: 'allowUnauthorizedCerts',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-ignore-ssl-issues
|
||||
description:
|
||||
'Whether to download the response even if SSL certificate validation is not possible',
|
||||
},
|
||||
{
|
||||
displayName: 'Request Format',
|
||||
name: 'requestFormat',
|
||||
type: 'options',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
name: 'GraphQL (Raw)',
|
||||
value: 'graphql',
|
||||
},
|
||||
{
|
||||
name: 'JSON',
|
||||
value: 'json',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestMethod: ['POST'],
|
||||
'@version': [1],
|
||||
},
|
||||
},
|
||||
default: 'graphql',
|
||||
description: 'The format for the query payload',
|
||||
},
|
||||
{
|
||||
displayName: 'Request Format',
|
||||
name: 'requestFormat',
|
||||
type: 'options',
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
name: 'JSON (Recommended)',
|
||||
value: 'json',
|
||||
description:
|
||||
'JSON object with query, variables, and operationName properties. The standard and most widely supported format for GraphQL requests.',
|
||||
},
|
||||
{
|
||||
name: 'GraphQL (Raw)',
|
||||
value: 'graphql',
|
||||
description:
|
||||
'Raw GraphQL query string. Not all servers support this format. Use JSON for better compatibility.',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestMethod: ['POST'],
|
||||
'@version': [{ _cnd: { gte: 1.1 } }],
|
||||
},
|
||||
},
|
||||
default: 'json',
|
||||
description: 'The request format for the query payload',
|
||||
},
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'GraphQL query',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
rows: 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Variables',
|
||||
name: 'variables',
|
||||
type: 'json',
|
||||
default: '',
|
||||
description: 'Query variables as JSON object',
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestFormat: ['json'],
|
||||
requestMethod: ['POST'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Operation Name',
|
||||
name: 'operationName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of operation to execute',
|
||||
displayOptions: {
|
||||
show: {
|
||||
requestFormat: ['json'],
|
||||
requestMethod: ['POST'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Response Format',
|
||||
name: 'responseFormat',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'JSON',
|
||||
value: 'json',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'string',
|
||||
},
|
||||
],
|
||||
default: 'json',
|
||||
description: 'The format in which the data gets returned from the URL',
|
||||
},
|
||||
{
|
||||
displayName: 'Response Data Property Name',
|
||||
name: 'dataPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
responseFormat: ['string'],
|
||||
},
|
||||
},
|
||||
description: 'Name of the property to which to write the response data',
|
||||
},
|
||||
|
||||
// Header Parameters
|
||||
{
|
||||
displayName: 'Headers',
|
||||
name: 'headerParametersUi',
|
||||
placeholder: 'Add Header',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
description: 'The headers to send',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'parameter',
|
||||
displayName: 'Header',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the header',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value to set for the header',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
let httpBasicAuth;
|
||||
let httpDigestAuth;
|
||||
let httpCustomAuth;
|
||||
let httpHeaderAuth;
|
||||
let httpQueryAuth;
|
||||
let oAuth1Api;
|
||||
let oAuth2Api;
|
||||
|
||||
try {
|
||||
httpBasicAuth = await this.getCredentials('httpBasicAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpCustomAuth = await this.getCredentials('httpCustomAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpDigestAuth = await this.getCredentials('httpDigestAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpHeaderAuth = await this.getCredentials('httpHeaderAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
httpQueryAuth = await this.getCredentials('httpQueryAuth');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
oAuth1Api = await this.getCredentials('oAuth1Api');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
try {
|
||||
oAuth2Api = await this.getCredentials('oAuth2Api');
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
let requestOptions: IRequestOptions;
|
||||
|
||||
const returnItems: INodeExecutionData[] = [];
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
const requestMethod = this.getNodeParameter(
|
||||
'requestMethod',
|
||||
itemIndex,
|
||||
'POST',
|
||||
) as IHttpRequestMethods;
|
||||
const endpoint = this.getNodeParameter('endpoint', itemIndex, '') as string;
|
||||
const requestFormat = this.getNodeParameter('requestFormat', itemIndex, 'json') as string;
|
||||
const responseFormat = this.getNodeParameter('responseFormat', 0) as string;
|
||||
const { parameter }: { parameter?: Array<{ name: string; value: string }> } =
|
||||
this.getNodeParameter('headerParametersUi', itemIndex, {}) as IDataObject;
|
||||
const headerParameters = (parameter || []).reduce(
|
||||
(result, item) => ({
|
||||
...result,
|
||||
[item.name]: item.value,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
requestOptions = {
|
||||
headers: {
|
||||
'content-type': `application/${requestFormat}`,
|
||||
...headerParameters,
|
||||
},
|
||||
method: requestMethod,
|
||||
uri: endpoint,
|
||||
simple: false,
|
||||
rejectUnauthorized: !this.getNodeParameter('allowUnauthorizedCerts', itemIndex, false),
|
||||
};
|
||||
|
||||
// Add credentials if any are set
|
||||
if (httpBasicAuth !== undefined) {
|
||||
requestOptions.auth = {
|
||||
user: httpBasicAuth.user as string,
|
||||
pass: httpBasicAuth.password as string,
|
||||
};
|
||||
}
|
||||
if (httpCustomAuth !== undefined) {
|
||||
const customAuth = jsonParse<IRequestOptionsSimplified>(
|
||||
(httpCustomAuth.json as string) || '{}',
|
||||
{ errorMessage: 'Invalid Custom Auth JSON' },
|
||||
);
|
||||
if (customAuth.headers) {
|
||||
requestOptions.headers = { ...requestOptions.headers, ...customAuth.headers };
|
||||
}
|
||||
if (customAuth.body) {
|
||||
requestOptions.body = { ...requestOptions.body, ...customAuth.body };
|
||||
}
|
||||
if (customAuth.qs) {
|
||||
requestOptions.qs = { ...requestOptions.qs, ...customAuth.qs };
|
||||
}
|
||||
}
|
||||
if (httpHeaderAuth !== undefined) {
|
||||
requestOptions.headers![httpHeaderAuth.name as string] = httpHeaderAuth.value;
|
||||
}
|
||||
if (httpQueryAuth !== undefined) {
|
||||
if (!requestOptions.qs) {
|
||||
requestOptions.qs = {};
|
||||
}
|
||||
requestOptions.qs[httpQueryAuth.name as string] = httpQueryAuth.value;
|
||||
}
|
||||
if (httpDigestAuth !== undefined) {
|
||||
requestOptions.auth = {
|
||||
user: httpDigestAuth.user as string,
|
||||
pass: httpDigestAuth.password as string,
|
||||
sendImmediately: false,
|
||||
};
|
||||
}
|
||||
|
||||
const gqlQuery = this.getNodeParameter('query', itemIndex, '') as string;
|
||||
if (requestMethod === 'GET') {
|
||||
requestOptions.qs = requestOptions.qs ?? {};
|
||||
requestOptions.qs.query = gqlQuery;
|
||||
}
|
||||
|
||||
if (requestFormat === 'json') {
|
||||
const variables = this.getNodeParameter('variables', itemIndex, {});
|
||||
|
||||
let parsedVariables;
|
||||
if (typeof variables === 'string') {
|
||||
try {
|
||||
parsedVariables = JSON.parse(variables || '{}');
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Using variables failed:\n${variables}\n\nWith error message:\n${error}`,
|
||||
{ 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 === '') {
|
||||
jsonBody.operationName = null;
|
||||
}
|
||||
|
||||
requestOptions.json = true;
|
||||
requestOptions.body = jsonBody;
|
||||
} else {
|
||||
requestOptions.body = gqlQuery;
|
||||
}
|
||||
|
||||
let response;
|
||||
// Now that the options are all set make the actual http request
|
||||
if (oAuth1Api !== undefined) {
|
||||
response = await this.helpers.requestOAuth1.call(this, 'oAuth1Api', requestOptions);
|
||||
} else if (oAuth2Api !== undefined) {
|
||||
response = await this.helpers.requestOAuth2.call(
|
||||
this,
|
||||
'oAuth2Api',
|
||||
{
|
||||
...requestOptions,
|
||||
// needed for the refresh mechanism to work properly
|
||||
resolveWithFullResponse: true,
|
||||
},
|
||||
{
|
||||
tokenType: 'Bearer',
|
||||
},
|
||||
);
|
||||
// since we are using `resolveWithFullResponse: true`, we need to grab the body
|
||||
response = response.body;
|
||||
} else {
|
||||
response = await this.helpers.request(requestOptions);
|
||||
}
|
||||
if (responseFormat === 'string') {
|
||||
const dataPropertyName = this.getNodeParameter('dataPropertyName', 0);
|
||||
returnItems.push({
|
||||
json: {
|
||||
[dataPropertyName]: response,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (typeof response === 'string') {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (error) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Response body is not valid JSON. Change "Response Format" to "String"',
|
||||
{ itemIndex },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(response as IDataObject),
|
||||
{ itemData: { item: itemIndex } },
|
||||
);
|
||||
returnItems.push(...executionData);
|
||||
}
|
||||
|
||||
// parse error string messages
|
||||
if (typeof response === 'string' && response.startsWith('{"errors":')) {
|
||||
try {
|
||||
const errorResponse = JSON.parse(response) as IDataObject;
|
||||
if (Array.isArray(errorResponse.errors)) {
|
||||
response = errorResponse;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
// throw from response object.errors[]
|
||||
if (typeof response === 'object' && response.errors) {
|
||||
const message =
|
||||
response.errors?.map((error: IDataObject) => error.message).join(', ') ||
|
||||
'Unexpected error';
|
||||
throw new NodeApiError(this.getNode(), response.errors as JsonObject, { message });
|
||||
}
|
||||
} catch (error) {
|
||||
if (!this.continueOnFail()) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const errorData = this.helpers.returnJsonArray({
|
||||
error: error.message,
|
||||
});
|
||||
const exectionErrorWithMetaData = this.helpers.constructExecutionMetaData(errorData, {
|
||||
itemData: { item: itemIndex },
|
||||
});
|
||||
returnItems.push(...exectionErrorWithMetaData);
|
||||
}
|
||||
}
|
||||
return [returnItems];
|
||||
}
|
||||
}
|
||||
BIN
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/graphql.png
Executable file
BIN
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/graphql.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 979 B |
113
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/test/GraphQL.node.test.ts
Executable file
113
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/test/GraphQL.node.test.ts
Executable file
@@ -0,0 +1,113 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
describe('GraphQL Node', () => {
|
||||
describe('valid request', () => {
|
||||
const baseUrl = 'https://api.n8n.io/';
|
||||
nock(baseUrl)
|
||||
.matchHeader('accept', 'application/json')
|
||||
.matchHeader('content-type', 'application/json')
|
||||
.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: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['workflow.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('invalid expression', () => {
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['workflow.error_invalid_expression.json'],
|
||||
});
|
||||
});
|
||||
|
||||
describe('oauth2 refresh token', () => {
|
||||
const credentials = {
|
||||
oAuth2Api: {
|
||||
scope: '',
|
||||
accessTokenUrl: 'http://test/token',
|
||||
clientId: 'dummy_client_id',
|
||||
clientSecret: 'dummy_client_secret',
|
||||
oauthTokenData: {
|
||||
access_token: 'dummy_access_token',
|
||||
refresh_token: 'dummy_refresh_token',
|
||||
},
|
||||
},
|
||||
};
|
||||
const baseUrl = 'http://test';
|
||||
nock(baseUrl)
|
||||
.post('/graphql', '{"query":"query { foo }","variables":{},"operationName":null}')
|
||||
.reply(401, {
|
||||
errors: [
|
||||
{
|
||||
message: 'Unauthorized',
|
||||
},
|
||||
],
|
||||
});
|
||||
nock(baseUrl)
|
||||
.post('/token', {
|
||||
refresh_token: 'dummy_refresh_token',
|
||||
grant_type: 'refresh_token',
|
||||
})
|
||||
.reply(200, {
|
||||
access_token: 'dummy_access_token',
|
||||
refresh_token: 'dummy_refresh_token',
|
||||
expires_in: 3600,
|
||||
});
|
||||
nock(baseUrl)
|
||||
.post('/graphql', '{"query":"query { foo }","variables":{},"operationName":null}')
|
||||
.reply(200, {
|
||||
data: {
|
||||
foo: 'bar',
|
||||
},
|
||||
});
|
||||
new NodeTestHarness().setupTests({
|
||||
workflowFiles: ['workflow.refresh_token.json'],
|
||||
credentials,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
85
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/test/workflow.json
Executable file
85
n8n-n8n-1.109.2/packages/nodes-base/nodes/GraphQL/test/workflow.json
Executable file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"meta": {
|
||||
"templateId": "216",
|
||||
"instanceId": "ee90fdf8d57662f949e6c691dc07fa0fd2f66e1eee28ed82ef06658223e67255"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "5e2ef15b-2c6c-412f-a9da-515b5211386e",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [420, 100]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"endpoint": "https://api.n8n.io/graphql",
|
||||
"requestFormat": "json",
|
||||
"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}"
|
||||
},
|
||||
"name": "Fetch Request Format JSON",
|
||||
"type": "n8n-nodes-base.graphql",
|
||||
"typeVersion": 1,
|
||||
"position": [700, 140],
|
||||
"id": "e1c750a0-8d6c-4e81-8111-3218e1e6e69f"
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Request Format JSON",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"name": "GraphQL Refersh Token",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "ae67b437-48c0-4c4b-9c8f-005b8911ca5f",
|
||||
"name": "When clicking ‘Execute workflow’",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [32, 80]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"endpoint": "http://test/graphql",
|
||||
"requestFormat": "json",
|
||||
"query": "query { foo }"
|
||||
},
|
||||
"name": "GraphQL",
|
||||
"type": "n8n-nodes-base.graphql",
|
||||
"typeVersion": 1,
|
||||
"position": [256, 80],
|
||||
"id": "6cb4c117-907a-40b9-b3ba-cd3756b1cb7b",
|
||||
"credentials": {
|
||||
"oAuth2Api": {
|
||||
"id": "PpmTbnw41Q2nqqoW",
|
||||
"name": "Dummy (local)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"GraphQL": [
|
||||
{
|
||||
"json": {
|
||||
"data": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "GraphQL",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "659c76f4-ac29-4c2f-bf40-70b476afbc1d",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "eeda9e3069aca300d1dfceeb64beb5b53d715db44a50461bbc5cb0cf6daa01e3"
|
||||
},
|
||||
"id": "O4IzQ2D7h7cfZtB4",
|
||||
"tags": []
|
||||
}
|
||||
Reference in New Issue
Block a user