mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(Google Chat Node): Updates (#12827)
Co-authored-by: Dana <152518854+dana-gill@users.noreply.github.com>
This commit is contained in:
parent
de49c23971
commit
e146ad021a
|
@ -0,0 +1,26 @@
|
||||||
|
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
const scopes = [
|
||||||
|
'https://www.googleapis.com/auth/chat.spaces',
|
||||||
|
'https://www.googleapis.com/auth/chat.messages',
|
||||||
|
'https://www.googleapis.com/auth/chat.memberships',
|
||||||
|
];
|
||||||
|
|
||||||
|
export class GoogleChatOAuth2Api implements ICredentialType {
|
||||||
|
name = 'googleChatOAuth2Api';
|
||||||
|
|
||||||
|
extends = ['googleOAuth2Api'];
|
||||||
|
|
||||||
|
displayName = 'Chat OAuth2 API';
|
||||||
|
|
||||||
|
documentationUrl = 'google/oauth-single-service';
|
||||||
|
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Scope',
|
||||||
|
name: 'scope',
|
||||||
|
type: 'hidden',
|
||||||
|
default: scopes.join(' '),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
|
@ -9,48 +9,70 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeApiError } from 'n8n-workflow';
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { getSendAndWaitConfig } from '../../../utils/sendAndWait/utils';
|
||||||
import { getGoogleAccessToken } from '../GenericFunctions';
|
import { getGoogleAccessToken } from '../GenericFunctions';
|
||||||
|
|
||||||
|
async function googleServiceAccountApiRequest(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
options: IRequestOptions,
|
||||||
|
noCredentials = false,
|
||||||
|
): Promise<any> {
|
||||||
|
if (noCredentials) {
|
||||||
|
return await this.helpers.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = await this.getCredentials('googleApi');
|
||||||
|
|
||||||
|
const { access_token } = await getGoogleAccessToken.call(this, credentials, 'chat');
|
||||||
|
options.headers!.Authorization = `Bearer ${access_token}`;
|
||||||
|
|
||||||
|
return await this.helpers.request(options);
|
||||||
|
}
|
||||||
|
|
||||||
export async function googleApiRequest(
|
export async function googleApiRequest(
|
||||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
method: IHttpRequestMethods,
|
method: IHttpRequestMethods,
|
||||||
resource: string,
|
resource: string,
|
||||||
|
body: IDataObject = {},
|
||||||
body: any = {},
|
|
||||||
qs: IDataObject = {},
|
qs: IDataObject = {},
|
||||||
uri?: string,
|
uri?: string,
|
||||||
noCredentials = false,
|
noCredentials = false,
|
||||||
encoding?: null | undefined,
|
encoding?: null | undefined,
|
||||||
): Promise<any> {
|
) {
|
||||||
const options: IRequestOptions = {
|
const options: IRequestOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
qs,
|
qs,
|
||||||
uri: uri || `https://chat.googleapis.com${resource}`,
|
uri: uri || `https://chat.googleapis.com${resource}`,
|
||||||
|
qsStringifyOptions: {
|
||||||
|
arrayFormat: 'repeat',
|
||||||
|
},
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Object.keys(body as IDataObject).length === 0) {
|
|
||||||
delete options.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encoding === null) {
|
if (encoding === null) {
|
||||||
options.encoding = null;
|
options.encoding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let responseData: IDataObject | undefined;
|
if (Object.keys(body).length === 0) {
|
||||||
try {
|
delete options.body;
|
||||||
if (noCredentials) {
|
}
|
||||||
responseData = await this.helpers.request(options);
|
|
||||||
} else {
|
|
||||||
const credentials = await this.getCredentials('googleApi');
|
|
||||||
|
|
||||||
const { access_token } = await getGoogleAccessToken.call(this, credentials, 'chat');
|
let responseData;
|
||||||
options.headers!.Authorization = `Bearer ${access_token}`;
|
|
||||||
responseData = await this.helpers.request(options);
|
try {
|
||||||
|
if (noCredentials || this.getNodeParameter('authentication', 0) === 'serviceAccount') {
|
||||||
|
responseData = await googleServiceAccountApiRequest.call(this, options, noCredentials);
|
||||||
|
} else {
|
||||||
|
responseData = await this.helpers.requestWithAuthentication.call(
|
||||||
|
this,
|
||||||
|
'googleChatOAuth2Api',
|
||||||
|
options,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||||
|
@ -59,6 +81,7 @@ export async function googleApiRequest(
|
||||||
|
|
||||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(responseData as IDataObject).length !== 0) {
|
if (Object.keys(responseData as IDataObject).length !== 0) {
|
||||||
return responseData;
|
return responseData;
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,3 +157,26 @@ export function getPagingParameters(resource: string, operation = 'getAll') {
|
||||||
];
|
];
|
||||||
return pagingParameters;
|
return pagingParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createSendAndWaitMessageBody(context: IExecuteFunctions) {
|
||||||
|
const config = getSendAndWaitConfig(context);
|
||||||
|
|
||||||
|
const instanceId = context.getInstanceId();
|
||||||
|
const attributionText = '_This_ _message_ _was_ _sent_ _automatically_ _with_';
|
||||||
|
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||||
|
'n8n-nodes-base.telegram',
|
||||||
|
)}${instanceId ? '_' + instanceId : ''}`;
|
||||||
|
const attribution = `${attributionText} _<${link}|n8n>_`;
|
||||||
|
|
||||||
|
const buttons: string[] = config.options.map(
|
||||||
|
(option) => `*<${`${config.url}?approved=${option.value}`}|${option.label}>*`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const text = `${config.message}\n\n\n${buttons.join(' ')}\n\n${attribution}`;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
text,
|
||||||
|
};
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
"node": "n8n-nodes-base.googleChat",
|
"node": "n8n-nodes-base.googleChat",
|
||||||
"nodeVersion": "1.0",
|
"nodeVersion": "1.0",
|
||||||
"codexVersion": "1.0",
|
"codexVersion": "1.0",
|
||||||
"categories": ["Communication"],
|
"categories": ["Communication", "HILT"],
|
||||||
|
"subcategories": {
|
||||||
|
"HILT": ["Human in the Loop"]
|
||||||
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"credentialDocumentation": [
|
"credentialDocumentation": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,7 +13,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
IRequestOptions,
|
IRequestOptions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
|
import { NodeConnectionType, NodeOperationError, SEND_AND_WAIT_OPERATION } from 'n8n-workflow';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
// attachmentFields,
|
// attachmentFields,
|
||||||
|
@ -27,10 +27,22 @@ import {
|
||||||
messageFields,
|
messageFields,
|
||||||
messageOperations,
|
messageOperations,
|
||||||
spaceFields,
|
spaceFields,
|
||||||
|
spaceIdProperty,
|
||||||
spaceOperations,
|
spaceOperations,
|
||||||
} from './descriptions';
|
} from './descriptions';
|
||||||
import { googleApiRequest, googleApiRequestAllItems, validateJSON } from './GenericFunctions';
|
import {
|
||||||
|
createSendAndWaitMessageBody,
|
||||||
|
googleApiRequest,
|
||||||
|
googleApiRequestAllItems,
|
||||||
|
validateJSON,
|
||||||
|
} from './GenericFunctions';
|
||||||
import type { IMessage, IMessageUi } from './MessageInterface';
|
import type { IMessage, IMessageUi } from './MessageInterface';
|
||||||
|
import { sendAndWaitWebhooksDescription } from '../../../utils/sendAndWait/descriptions';
|
||||||
|
import {
|
||||||
|
configureWaitTillDate,
|
||||||
|
getSendAndWaitProperties,
|
||||||
|
sendAndWaitWebhook,
|
||||||
|
} from '../../../utils/sendAndWait/utils';
|
||||||
|
|
||||||
export class GoogleChat implements INodeType {
|
export class GoogleChat implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
|
@ -46,14 +58,46 @@ export class GoogleChat implements INodeType {
|
||||||
},
|
},
|
||||||
inputs: [NodeConnectionType.Main],
|
inputs: [NodeConnectionType.Main],
|
||||||
outputs: [NodeConnectionType.Main],
|
outputs: [NodeConnectionType.Main],
|
||||||
|
webhooks: sendAndWaitWebhooksDescription,
|
||||||
credentials: [
|
credentials: [
|
||||||
{
|
{
|
||||||
name: 'googleApi',
|
name: 'googleApi',
|
||||||
required: true,
|
required: true,
|
||||||
testedBy: 'testGoogleTokenAuth',
|
testedBy: 'testGoogleTokenAuth',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: ['serviceAccount'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'googleChatOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
authentication: ['oAuth2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
properties: [
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Authentication',
|
||||||
|
name: 'authentication',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'OAuth2 (recommended)',
|
||||||
|
value: 'oAuth2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Service Account',
|
||||||
|
value: 'serviceAccount',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'serviceAccount',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Resource',
|
displayName: 'Resource',
|
||||||
name: 'resource',
|
name: 'resource',
|
||||||
|
@ -100,9 +144,16 @@ export class GoogleChat implements INodeType {
|
||||||
...messageFields,
|
...messageFields,
|
||||||
...spaceOperations,
|
...spaceOperations,
|
||||||
...spaceFields,
|
...spaceFields,
|
||||||
|
...getSendAndWaitProperties([spaceIdProperty], 'message', undefined, {
|
||||||
|
noButtonStyle: true,
|
||||||
|
defaultApproveLabel: '✅ Approve',
|
||||||
|
defaultDisapproveLabel: '❌ Decline',
|
||||||
|
}).filter((p) => p.name !== 'subject'),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
webhook = sendAndWaitWebhook;
|
||||||
|
|
||||||
methods = {
|
methods = {
|
||||||
loadOptions: {
|
loadOptions: {
|
||||||
// Get all the spaces to display them to user so that they can
|
// Get all the spaces to display them to user so that they can
|
||||||
|
@ -196,6 +247,19 @@ export class GoogleChat implements INodeType {
|
||||||
let responseData;
|
let responseData;
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
|
||||||
|
if (resource === 'message' && operation === SEND_AND_WAIT_OPERATION) {
|
||||||
|
const spaceId = this.getNodeParameter('spaceId', 0) as string;
|
||||||
|
const body = createSendAndWaitMessageBody(this);
|
||||||
|
|
||||||
|
await googleApiRequest.call(this, 'POST', `/v1/${spaceId}/messages`, body);
|
||||||
|
|
||||||
|
const waitTill = configureWaitTillDate(this);
|
||||||
|
|
||||||
|
await this.putExecutionToWait(waitTill);
|
||||||
|
return [this.getInputData()];
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
try {
|
try {
|
||||||
if (resource === 'media') {
|
if (resource === 'media') {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { INodeProperties } from 'n8n-workflow';
|
import { SEND_AND_WAIT_OPERATION, type INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
export const messageOperations: INodeProperties[] = [
|
export const messageOperations: INodeProperties[] = [
|
||||||
{
|
{
|
||||||
|
@ -30,6 +30,12 @@ export const messageOperations: INodeProperties[] = [
|
||||||
description: 'Get a message',
|
description: 'Get a message',
|
||||||
action: 'Get a message',
|
action: 'Get a message',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Send and Wait for Response',
|
||||||
|
value: SEND_AND_WAIT_OPERATION,
|
||||||
|
description: 'Send a message and wait for response',
|
||||||
|
action: 'Send message and wait for response',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Update',
|
name: 'Update',
|
||||||
value: 'update',
|
value: 'update',
|
||||||
|
@ -41,27 +47,31 @@ export const messageOperations: INodeProperties[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const spaceIdProperty: INodeProperties = {
|
||||||
|
displayName: 'Space Name or ID',
|
||||||
|
name: 'spaceId',
|
||||||
|
type: 'options',
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getSpaces',
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Space resource name, in the form "spaces/*". Example: spaces/AAAAMpdlehY. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||||
|
};
|
||||||
|
|
||||||
export const messageFields: INodeProperties[] = [
|
export const messageFields: INodeProperties[] = [
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* message:create */
|
/* message:create */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
{
|
{
|
||||||
displayName: 'Space Name or ID',
|
...spaceIdProperty,
|
||||||
name: 'spaceId',
|
|
||||||
type: 'options',
|
|
||||||
required: true,
|
|
||||||
typeOptions: {
|
|
||||||
loadOptionsMethod: 'getSpaces',
|
|
||||||
},
|
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
resource: ['message'],
|
resource: ['message'],
|
||||||
operation: ['create'],
|
operation: ['create'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
default: '',
|
|
||||||
description:
|
|
||||||
'Space resource name, in the form "spaces/*". Example: spaces/AAAAMpdlehY. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'JSON Parameters',
|
displayName: 'JSON Parameters',
|
||||||
|
|
|
@ -1 +1,19 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 479.4 499.98"><path d="M121.24 275.69v-162.5h-71a37.86 37.86 0 0 0-37.8 37.9V487c0 16.9 20.4 25.3 32.3 13.4l78.1-78.1h222.4a37.77 37.77 0 0 0 37.8-37.8v-71h-223.9a37.86 37.86 0 0 1-37.9-37.81" style="fill:#00ac47" transform="translate(-12.44 -5.99)"/><path d="M454 6H159.14a37.77 37.77 0 0 0-37.8 37.8v69.4h223.9A37.77 37.77 0 0 1 383 151v162.4h71a37.77 37.77 0 0 0 37.8-37.8V43.79A37.77 37.77 0 0 0 454 6" style="fill:#5bb974" transform="translate(-12.44 -5.99)"/><path d="M345.24 113.19h-224v162.4a37.77 37.77 0 0 0 37.8 37.8h223.9v-162.3a37.71 37.71 0 0 0-37.7-37.9" style="fill:#00832d" transform="translate(-12.44 -5.99)"/></svg>
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0.00 0.00 311.00 320.00">
|
||||||
|
<g stroke-width="2.00" fill="none" stroke-linecap="butt">
|
||||||
|
<path stroke="#7e916f" vector-effect="non-scaling-stroke" d=" M 76.37 0.51 L 76.38 76.98"/>
|
||||||
|
<path stroke="#1375eb" vector-effect="non-scaling-stroke" d=" M 76.38 76.98 L 0.00 76.96"/>
|
||||||
|
<path stroke="#f3801d" vector-effect="non-scaling-stroke" d=" M 235.08 1.09 Q 234.92 1.15 234.81 1.22 Q 234.64 1.31 234.64 1.50 L 234.62 77.01"/>
|
||||||
|
<path stroke="#7eb426" vector-effect="non-scaling-stroke" d=" M 234.62 77.01 Q 234.60 77.01 234.57 77.01"/>
|
||||||
|
<path stroke="#91a080" vector-effect="non-scaling-stroke" d=" M 76.41 77.01 Q 76.40 77.00 76.38 76.98"/>
|
||||||
|
<path stroke="#75783e" vector-effect="non-scaling-stroke" d=" M 310.53 76.77 L 234.62 77.01"/>
|
||||||
|
<path stroke="#138495" vector-effect="non-scaling-stroke" d=" M 76.43 182.69 L 0.00 182.67"/>
|
||||||
|
<path stroke="#00983a" vector-effect="non-scaling-stroke" d=" M 76.44 259.13 L 76.43 220.85"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#0066da" d=" M 76.37 0.51 L 76.38 76.98 L 0.00 76.96 L 0.00 20.77 Q 0.85 14.81 3.53 10.76 Q 10.14 0.74 22.75 0.67 Q 49.41 0.53 76.37 0.51 Z"/>
|
||||||
|
<path fill="#fbbc04" d=" M 76.37 0.51 L 233.79 0.53 A 1.61 1.57 -26.7 0 1 234.71 0.82 L 235.08 1.09 Q 234.92 1.15 234.81 1.22 Q 234.64 1.31 234.64 1.50 L 234.62 77.01 Q 234.60 77.01 234.57 77.01 L 76.41 77.01 Q 76.40 77.00 76.38 76.98 L 76.37 0.51 Z"/>
|
||||||
|
<path fill="#ea4335" d=" M 235.08 1.09 L 310.53 76.77 L 234.62 77.01 L 234.64 1.50 Q 234.64 1.31 234.81 1.22 Q 234.92 1.15 235.08 1.09 Z"/>
|
||||||
|
<path fill="#2684fc" d=" M 0.00 76.96 L 76.38 76.98 Q 76.40 77.00 76.41 77.01 L 76.43 182.69 L 0.00 182.67 L 0.00 76.96 Z"/>
|
||||||
|
<path fill="#00ac47" d=" M 310.53 76.77 L 311.00 77.11 L 311.00 239.01 Q 308.34 253.54 295.94 257.78 Q 291.52 259.30 282.91 259.28 Q 227.02 259.19 169.99 259.11 Q 161.71 259.10 153.19 259.23 Q 152.72 259.24 152.39 259.57 Q 124.49 287.34 96.39 315.59 C 93.52 318.48 90.27 320.09 86.15 319.48 Q 80.39 318.63 77.66 313.54 Q 76.51 311.38 76.49 305.66 Q 76.42 282.47 76.44 259.13 L 76.43 220.85 L 114.21 183.07 A 1.79 1.77 22.3 0 1 115.47 182.55 L 233.77 182.59 A 0.83 0.83 0.0 0 0 234.60 181.76 L 234.57 77.01 Q 234.60 77.01 234.62 77.01 L 310.53 76.77 Z"/>
|
||||||
|
<path fill="#00832d" d=" M 76.43 182.69 L 76.43 220.85 L 76.44 259.13 Q 52.47 259.27 28.91 259.22 Q 19.09 259.20 14.76 257.68 Q 2.62 253.44 0.00 238.88 L 0.00 182.67 L 76.43 182.69 Z"/>
|
||||||
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,97 @@
|
||||||
|
import type { MockProxy } from 'jest-mock-extended';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { type IExecuteFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as googleHelpers from '../../GenericFunctions';
|
||||||
|
import { googleApiRequest } from '../GenericFunctions';
|
||||||
|
|
||||||
|
jest.mock('../../GenericFunctions', () => ({
|
||||||
|
...jest.requireActual('../../GenericFunctions'),
|
||||||
|
getGoogleAccessToken: jest.fn().mockResolvedValue({ access_token: 'mock-access-token' }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Test GoogleChat, googleApiRequest', () => {
|
||||||
|
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||||
|
|
||||||
|
mockExecuteFunctions.helpers = {
|
||||||
|
requestWithAuthentication: jest.fn().mockResolvedValue({}),
|
||||||
|
request: jest.fn().mockResolvedValue({}),
|
||||||
|
} as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call requestWithAuthentication when authentication set to OAuth2', async () => {
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('oAuth2'); // authentication
|
||||||
|
|
||||||
|
const result = await googleApiRequest.call(mockExecuteFunctions, 'POST', '/test-resource', {
|
||||||
|
text: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
|
||||||
|
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith(
|
||||||
|
'googleChatOAuth2Api',
|
||||||
|
{
|
||||||
|
body: { text: 'test' },
|
||||||
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
||||||
|
json: true,
|
||||||
|
method: 'POST',
|
||||||
|
qs: {},
|
||||||
|
qsStringifyOptions: { arrayFormat: 'repeat' },
|
||||||
|
uri: 'https://chat.googleapis.com/test-resource',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call request when authentication set to serviceAccount', async () => {
|
||||||
|
const mockCredentials = {
|
||||||
|
email: 'test@example.com',
|
||||||
|
privateKey: 'private-key',
|
||||||
|
};
|
||||||
|
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('serviceAccount');
|
||||||
|
mockExecuteFunctions.getCredentials.mockResolvedValueOnce(mockCredentials);
|
||||||
|
|
||||||
|
const result = await googleApiRequest.call(mockExecuteFunctions, 'GET', '/test-resource');
|
||||||
|
|
||||||
|
expect(googleHelpers.getGoogleAccessToken).toHaveBeenCalledWith(mockCredentials, 'chat');
|
||||||
|
expect(mockExecuteFunctions.helpers.request).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
headers: expect.objectContaining({
|
||||||
|
Authorization: 'Bearer mock-access-token',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call request when noCredentials equals true', async () => {
|
||||||
|
const result = await googleApiRequest.call(
|
||||||
|
mockExecuteFunctions,
|
||||||
|
'GET',
|
||||||
|
'/test-resource',
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockExecuteFunctions.helpers.request).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockExecuteFunctions.helpers.request).toHaveBeenCalledWith({
|
||||||
|
headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
|
||||||
|
json: true,
|
||||||
|
method: 'GET',
|
||||||
|
qs: {},
|
||||||
|
qsStringifyOptions: { arrayFormat: 'repeat' },
|
||||||
|
uri: 'https://chat.googleapis.com/test-resource',
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,60 @@
|
||||||
|
import type { MockProxy } from 'jest-mock-extended';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { type INode, SEND_AND_WAIT_OPERATION, type IExecuteFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import * as genericFunctions from '../../GenericFunctions';
|
||||||
|
import { GoogleChat } from '../../GoogleChat.node';
|
||||||
|
|
||||||
|
jest.mock('../../GenericFunctions', () => {
|
||||||
|
const originalModule = jest.requireActual('../../GenericFunctions');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
googleApiRequest: jest.fn(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test GoogleChat, message => sendAndWait', () => {
|
||||||
|
let googleChat: GoogleChat;
|
||||||
|
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
googleChat = new GoogleChat();
|
||||||
|
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send message and put execution to wait', async () => {
|
||||||
|
const items = [{ json: { data: 'test' } }];
|
||||||
|
//node
|
||||||
|
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('message');
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(SEND_AND_WAIT_OPERATION);
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('spaceID');
|
||||||
|
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>());
|
||||||
|
mockExecuteFunctions.getInstanceId.mockReturnValue('instanceId');
|
||||||
|
|
||||||
|
//getSendAndWaitConfig
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('my message');
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('my subject');
|
||||||
|
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('http://localhost/waiting-webhook');
|
||||||
|
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('nodeID');
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({});
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('approval');
|
||||||
|
|
||||||
|
// configureWaitTillDate
|
||||||
|
mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({}); //options.limitWaitTime.values
|
||||||
|
|
||||||
|
const result = await googleChat.execute.call(mockExecuteFunctions);
|
||||||
|
|
||||||
|
expect(result).toEqual([items]);
|
||||||
|
expect(genericFunctions.googleApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockExecuteFunctions.putExecutionToWait).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith('POST', '/v1/spaceID/messages', {
|
||||||
|
text: 'my message\n\n\n*<http://localhost/waiting-webhook/nodeID?approved=true|Approve>*\n\n_This_ _message_ _was_ _sent_ _automatically_ _with_ _<https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.telegram_instanceId|n8n>_',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -132,6 +132,7 @@
|
||||||
"dist/credentials/GoogleBigQueryOAuth2Api.credentials.js",
|
"dist/credentials/GoogleBigQueryOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleBooksOAuth2Api.credentials.js",
|
"dist/credentials/GoogleBooksOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleCalendarOAuth2Api.credentials.js",
|
"dist/credentials/GoogleCalendarOAuth2Api.credentials.js",
|
||||||
|
"dist/credentials/GoogleChatOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js",
|
"dist/credentials/GoogleCloudNaturalLanguageOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleCloudStorageOAuth2Api.credentials.js",
|
"dist/credentials/GoogleCloudStorageOAuth2Api.credentials.js",
|
||||||
"dist/credentials/GoogleContactsOAuth2Api.credentials.js",
|
"dist/credentials/GoogleContactsOAuth2Api.credentials.js",
|
||||||
|
|
Loading…
Reference in a new issue