diff --git a/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts b/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts index d8243627f5..76b1d3fedc 100644 --- a/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/MicrosoftSharePointOAuth2Api.credentials.ts @@ -1,25 +1,14 @@ -import type { Icon, ICredentialType, INodeProperties } from 'n8n-workflow'; +import type { ICredentialType, INodeProperties } from 'n8n-workflow'; export class MicrosoftSharePointOAuth2Api implements ICredentialType { name = 'microsoftSharePointOAuth2Api'; extends = ['microsoftOAuth2Api']; - icon: Icon = { - light: 'file:icons/SharePoint.svg', - dark: 'file:icons/SharePoint.svg', - }; - displayName = 'Microsoft SharePoint OAuth2 API'; documentationUrl = 'microsoft'; - httpRequestNode = { - name: 'Microsoft SharePoint', - docsUrl: 'https://learn.microsoft.com/en-us/sharepoint/dev/apis/sharepoint-rest-graph', - apiBaseUrlPlaceholder: 'https://{subdomain}.sharepoint.com/_api/v2.0/', - }; - properties: INodeProperties[] = [ { displayName: 'Scope', diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.ts new file mode 100644 index 0000000000..249e758c57 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.ts @@ -0,0 +1,68 @@ +import type { INodeType, INodeTypeDescription } from 'n8n-workflow'; +import { NodeConnectionTypes } from 'n8n-workflow'; + +import { file, item, list } from './descriptions'; +import { listSearch, resourceMapping } from './methods'; + +export class MicrosoftSharePoint implements INodeType { + description: INodeTypeDescription = { + displayName: 'Microsoft SharePoint', + name: 'microsoftSharePoint', + icon: { + light: 'file:microsoftSharePoint.svg', + dark: 'file:microsoftSharePoint.svg', + }, + group: ['transform'], + version: 1, + subtitle: '={{ $parameter["operation"] + ": " + $parameter["resource"] }}', + description: 'Interact with Microsoft SharePoint API', + defaults: { + name: 'Microsoft SharePoint', + }, + usableAsTool: true, + inputs: [NodeConnectionTypes.Main], + outputs: [NodeConnectionTypes.Main], + credentials: [ + { + name: 'microsoftSharePointOAuth2Api', + required: true, + }, + ], + requestDefaults: { + baseURL: '=https://{{ $credentials.subdomain }}.sharepoint.com/_api/v2.0/', + ignoreHttpStatusErrors: true, + }, + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'File', + value: 'file', + }, + { + name: 'Item', + value: 'item', + }, + { + name: 'List', + value: 'list', + }, + ], + default: 'file', + }, + + ...file.description, + ...item.description, + ...list.description, + ], + }; + + methods = { + listSearch, + resourceMapping, + }; +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharepoint.node.json b/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharepoint.node.json new file mode 100644 index 0000000000..916b05164a --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/MicrosoftSharepoint.node.json @@ -0,0 +1,18 @@ +{ + "node": "n8n-nodes-base.microsoftSharePoint", + "nodeVersion": "1.0", + "codexVersion": "1.0", + "categories": ["ECM"], + "resources": { + "credentialDocumentation": [ + { + "url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/" + } + ], + "primaryDocumentation": [ + { + "url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftsharepoint/" + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/common.descriptions.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/common.descriptions.ts new file mode 100644 index 0000000000..4a0afe46c2 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/common.descriptions.ts @@ -0,0 +1,156 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const untilFolderSelected = { folder: [''] }; + +export const untilItemSelected = { item: [''] }; + +export const untilListSelected = { list: [''] }; + +export const untilSiteSelected = { site: [''] }; + +export const fileRLC: INodeProperties = { + displayName: 'File', + name: 'file', + default: { + mode: 'list', + value: '', + }, + description: 'Select the file to download', + modes: [ + { + displayName: 'From List', + name: 'list', + type: 'list', + typeOptions: { + searchListMethod: 'getFiles', + searchable: true, + }, + }, + { + displayName: 'By ID', + name: 'id', + placeholder: 'e.g. mysite', + type: 'string', + }, + ], + placeholder: 'eg. my-file.pdf', + required: true, + type: 'resourceLocator', +}; + +export const folderRLC: INodeProperties = { + displayName: 'Parent Folder', + name: 'folder', + default: { + mode: 'list', + value: '', + }, + description: 'Select the folder to update the file in', + modes: [ + { + displayName: 'From List', + name: 'list', + type: 'list', + typeOptions: { + searchListMethod: 'getFolders', + searchable: true, + }, + }, + { + displayName: 'By ID', + name: 'id', + placeholder: 'e.g. myfolder', + type: 'string', + }, + ], + placeholder: '/ (Library root)', + required: true, + type: 'resourceLocator', +}; + +export const itemRLC: INodeProperties = { + displayName: 'Item', + name: 'item', + default: { + mode: 'list', + value: '', + }, + description: 'Select the item you want to delete', + modes: [ + { + displayName: 'From List', + name: 'list', + type: 'list', + typeOptions: { + searchListMethod: 'getItems', + searchable: true, + }, + }, + { + displayName: 'By ID', + name: 'id', + placeholder: 'e.g. 1', + type: 'string', + }, + ], + required: true, + type: 'resourceLocator', +}; + +export const listRLC: INodeProperties = { + displayName: 'List', + name: 'list', + default: { + mode: 'list', + value: '', + }, + description: 'Select the list you want to retrieve', + modes: [ + { + displayName: 'From List', + name: 'list', + type: 'list', + typeOptions: { + searchListMethod: 'getLists', + searchable: true, + }, + }, + { + displayName: 'By ID', + name: 'id', + placeholder: 'e.g. mylist', + type: 'string', + }, + ], + required: true, + type: 'resourceLocator', +}; + +export const siteRLC: INodeProperties = { + displayName: 'Site', + name: 'site', + default: { + mode: 'list', + value: '', + }, + description: 'Select the site to retrieve folders from', + modes: [ + { + displayName: 'From List', + name: 'list', + type: 'list', + typeOptions: { + searchListMethod: 'getSites', + searchable: true, + }, + }, + { + displayName: 'By ID', + name: 'id', + placeholder: 'e.g. mysite', + type: 'string', + }, + ], + required: true, + type: 'resourceLocator', +}; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/File.resource.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/File.resource.ts new file mode 100644 index 0000000000..4b1add3589 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/File.resource.ts @@ -0,0 +1,74 @@ +import type { INodeProperties } from 'n8n-workflow'; + +import * as download from './download.operation'; +import * as update from './update.operation'; +import * as upload from './upload.operation'; +import { downloadFilePostReceive, handleErrorPostReceive } from '../../helpers/utils'; + +export const description: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['file'], + }, + }, + options: [ + { + name: 'Download', + value: 'download', + description: 'Download a file', + routing: { + request: { + method: 'GET', + url: '=/sites/{{ $parameter["site"] }}/drive/items/{{ $parameter["file"] }}/content', + json: false, + encoding: 'arraybuffer', + }, + output: { + postReceive: [handleErrorPostReceive, downloadFilePostReceive], + }, + }, + action: 'Download file', + }, + { + name: 'Update', + value: 'update', + description: 'Update a file', + routing: { + request: { + method: 'PATCH', + url: '=/sites/{{ $parameter["site"] }}/drive/items/{{ $parameter["file"] }}', + }, + output: { + postReceive: [handleErrorPostReceive], + }, + }, + action: 'Update file', + }, + { + name: 'Upload', + value: 'upload', + description: 'Upload an existing file', + routing: { + request: { + method: 'PUT', + url: '=/sites/{{ $parameter["site"] }}/drive/items/{{ $parameter["folder"] }}:/{{ $parameter["fileName"] }}:/content', + }, + output: { + postReceive: [handleErrorPostReceive], + }, + }, + action: 'Upload file', + }, + ], + default: 'download', + }, + + ...download.description, + ...update.description, + ...upload.description, +]; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/download.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/download.operation.ts new file mode 100644 index 0000000000..73ad1cfb3b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/download.operation.ts @@ -0,0 +1,44 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { + fileRLC, + folderRLC, + siteRLC, + untilFolderSelected, + untilSiteSelected, +} from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve folders from', + }, + { + ...folderRLC, + description: 'Select the folder to download the file from', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + ...fileRLC, + description: 'Select the file to download', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilFolderSelected, + }, + }, + }, +]; + +const displayOptions = { + show: { + resource: ['file'], + operation: ['download'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/update.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/update.operation.ts new file mode 100644 index 0000000000..37fed61fd2 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/update.operation.ts @@ -0,0 +1,122 @@ +import { + updateDisplayOptions, + type IExecuteSingleFunctions, + type IN8nHttpFullResponse, + type INodeExecutionData, + type INodeProperties, +} from 'n8n-workflow'; + +import { microsoftSharePointApiRequest } from '../../transport'; +import { + fileRLC, + folderRLC, + siteRLC, + untilFolderSelected, + untilSiteSelected, +} from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve folders from', + }, + { + ...folderRLC, + description: 'Select the folder to update the file in', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + ...fileRLC, + description: 'Select the file to update', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilFolderSelected, + }, + }, + }, + { + displayName: 'Updated File Name', + name: 'fileName', + default: '', + description: 'If not specified, the original file name will be used', + placeholder: 'e.g. My New File', + routing: { + send: { + property: 'name', + type: 'body', + value: '={{ $value }}', + }, + }, + type: 'string', + }, + { + displayName: 'Change File Content', + name: 'changeFileContent', + default: false, + description: 'Whether to update the file contents', + placeholder: 'e.g. My New File', + required: true, + type: 'boolean', + }, + { + displayName: 'Updated File Contents', + name: 'fileContents', + default: '', + description: + 'Find the name of input field containing the binary data to update the file in the Input panel on the left, in the Binary tab', + displayOptions: { + show: { + changeFileContent: [true], + }, + }, + hint: 'The name of the input field containing the binary file data to update the file', + placeholder: 'data', + required: true, + routing: { + output: { + postReceive: [ + async function ( + this: IExecuteSingleFunctions, + items: INodeExecutionData[], + _response: IN8nHttpFullResponse, + ): Promise { + for (const item of items) { + const site = this.getNodeParameter('site', undefined, { + extractValue: true, + }) as string; + const file = this.getNodeParameter('file', undefined, { + extractValue: true, + }) as string; + const binaryProperty = this.getNodeParameter('fileContents') as string; + this.helpers.assertBinaryData(binaryProperty); + const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(binaryProperty); + const response = await microsoftSharePointApiRequest.call( + this, + 'PUT', + `/sites/${site}/drive/items/${file}/content`, + binaryDataBuffer, + ); + item.json = response; + } + return items; + }, + ], + }, + }, + type: 'string', + }, +]; + +const displayOptions = { + show: { + resource: ['file'], + operation: ['update'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/upload.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/upload.operation.ts new file mode 100644 index 0000000000..6ba5a52abb --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/file/upload.operation.ts @@ -0,0 +1,54 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { uploadFilePreSend } from '../../helpers/utils'; +import { folderRLC, siteRLC, untilSiteSelected } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve folders from', + }, + { + ...folderRLC, + description: 'Select the folder to upload the file to', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + displayName: 'File Name', + name: 'fileName', + default: '', + description: 'The name of the file being uploaded', + placeholder: 'e.g. My New File', + required: true, + type: 'string', + }, + { + displayName: 'File Contents', + name: 'fileContents', + default: '', + description: + 'Find the name of input field containing the binary data to upload the file in the Input panel on the left, in the Binary tab', + hint: 'The name of the input field containing the binary file data to update the file', + placeholder: 'data', + required: true, + routing: { + send: { + preSend: [uploadFilePreSend], + }, + }, + type: 'string', + }, +]; + +const displayOptions = { + show: { + resource: ['file'], + operation: ['upload'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/index.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/index.ts new file mode 100644 index 0000000000..323bf10bd0 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/index.ts @@ -0,0 +1,3 @@ +export * as file from './file/File.resource'; +export * as item from './item/Item.resource'; +export * as list from './list/List.resource'; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/Item.resource.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/Item.resource.ts new file mode 100644 index 0000000000..e8340fe4db --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/Item.resource.ts @@ -0,0 +1,141 @@ +import type { INodeProperties } from 'n8n-workflow'; + +import * as create from './create.operation'; +import * as del from './delete.operation'; +import * as get from './get.operation'; +import * as getAll from './getAll.operation'; +import * as update from './update.operation'; +import * as upsert from './upsert.operation'; +import { handleErrorPostReceive, simplifyItemPostReceive } from '../../helpers/utils'; + +export const description: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['item'], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an item in an existing list', + routing: { + request: { + method: 'POST', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}/items', + }, + output: { + postReceive: [handleErrorPostReceive], + }, + }, + action: 'Create item in a list', + }, + { + name: 'Create or Update', + value: 'upsert', + description: 'Create a new record, or update the current one if it already exists (upsert)', + routing: { + request: { + method: 'POST', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}/items', + }, + output: { + postReceive: [handleErrorPostReceive], + }, + }, + action: 'Create or update item (upsert)', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete an item from a list', + routing: { + request: { + method: 'DELETE', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}/items/{{ $parameter["item"] }}', + }, + output: { + postReceive: [ + handleErrorPostReceive, + { + type: 'set', + properties: { + value: '={{ { "deleted": true } }}', + }, + }, + ], + }, + }, + action: 'Delete an item', + }, + { + name: 'Get', + value: 'get', + description: 'Retrieve an item from a list', + routing: { + request: { + ignoreHttpStatusErrors: true, + method: 'GET', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}/items/{{ $parameter["item"] }}', + }, + output: { + postReceive: [handleErrorPostReceive, simplifyItemPostReceive], + }, + }, + action: 'Get an item', + }, + { + name: 'Get Many', + value: 'getAll', + description: 'Get specific items in a list or list many items', + routing: { + request: { + method: 'GET', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}/items', + }, + output: { + postReceive: [ + handleErrorPostReceive, + { + type: 'rootProperty', + properties: { + property: 'value', + }, + }, + simplifyItemPostReceive, + ], + }, + }, + action: 'Get many items', + }, + { + name: 'Update', + value: 'update', + description: 'Update an item in an existing list', + routing: { + request: { + method: 'PATCH', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}/items', + }, + output: { + postReceive: [handleErrorPostReceive], + }, + }, + action: 'Update item in a list', + }, + ], + default: 'getAll', + }, + + ...create.description, + ...del.description, + ...get.description, + ...getAll.description, + ...update.description, + ...upsert.description, +]; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/create.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/create.operation.ts new file mode 100644 index 0000000000..3860af6616 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/create.operation.ts @@ -0,0 +1,77 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { itemColumnsPreSend } from '../../helpers/utils'; +import { listRLC, siteRLC, untilListSelected, untilSiteSelected } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to create an item in', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + displayName: + 'Due to API restrictions, the following column types cannot be updated: Hyperlink, Location, Metadata', + name: 'noticeUnsupportedFields', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + type: 'notice', + default: '', + }, + { + displayName: 'Columns', + name: 'columns', + default: { + mappingMode: 'defineBelow', + value: null, + }, + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + noDataExpression: true, + required: true, + routing: { + send: { + preSend: [itemColumnsPreSend], + }, + }, + type: 'resourceMapper', + typeOptions: { + loadOptionsDependsOn: ['site.value', 'list.value'], + resourceMapper: { + resourceMapperMethod: 'getMappingColumns', + mode: 'add', + fieldWords: { + singular: 'column', + plural: 'columns', + }, + addAllFields: true, + multiKeyMatch: false, + }, + }, + }, +]; + +const displayOptions = { + show: { + resource: ['item'], + operation: ['create'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/delete.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/delete.operation.ts new file mode 100644 index 0000000000..6fe55c4bdb --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/delete.operation.ts @@ -0,0 +1,44 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { + itemRLC, + listRLC, + siteRLC, + untilListSelected, + untilSiteSelected, +} from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to delete an item in', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + ...itemRLC, + description: 'Select the item you want to delete', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + }, +]; + +const displayOptions = { + show: { + resource: ['item'], + operation: ['delete'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/get.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/get.operation.ts new file mode 100644 index 0000000000..b89a503d73 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/get.operation.ts @@ -0,0 +1,69 @@ +import type { IExecuteSingleFunctions, IHttpRequestOptions, INodeProperties } from 'n8n-workflow'; +import { updateDisplayOptions } from 'n8n-workflow'; + +import { + itemRLC, + listRLC, + siteRLC, + untilListSelected, + untilSiteSelected, +} from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to retrieve an item from', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + ...itemRLC, + description: 'Select the item you want to get', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + }, + { + displayName: 'Simplify', + name: 'simplify', + default: true, + routing: { + send: { + preSend: [ + async function ( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, + ): Promise { + const simplify = this.getNodeParameter('simplify', false) as boolean; + if (simplify) { + requestOptions.qs ??= {}; + requestOptions.qs.$select = 'id,createdDateTime,lastModifiedDateTime,webUrl'; + requestOptions.qs.$expand = 'fields(select=Title)'; + } + return requestOptions; + }, + ], + }, + }, + type: 'boolean', + }, +]; + +const displayOptions = { + show: { + resource: ['item'], + operation: ['get'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/getAll.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/getAll.operation.ts new file mode 100644 index 0000000000..2691cd2058 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/getAll.operation.ts @@ -0,0 +1,186 @@ +import type { IExecuteSingleFunctions, IHttpRequestOptions, INodeProperties } from 'n8n-workflow'; +import { updateDisplayOptions } from 'n8n-workflow'; + +import { itemGetAllFieldsPreSend } from '../../helpers/utils'; +import { listRLC, siteRLC, untilSiteSelected } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to search for items in', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + displayName: 'Filter by Formula', + name: 'filter', + default: '', + description: + 'The formula will be evaluated for each record. More info.', + hint: 'If empty, all the items will be returned', + placeholder: "e.g. fields/Title eq 'item1'", + routing: { + send: { + property: '$filter', + type: 'query', + value: '={{ $value ? $value : undefined }}', + }, + }, + type: 'string', + }, + { + displayName: 'Return All', + name: 'returnAll', + default: false, + description: 'Whether to return all results or only up to a given limit', + routing: { + send: { + paginate: '={{ $value }}', + }, + operations: { + pagination: { + type: 'generic', + properties: { + continue: '={{ !!$response.body?.["@odata.nextLink"] }}', + request: { + url: '={{ $response.body?.["@odata.nextLink"] ?? $request.url }}', + qs: { + $select: + '={{ !!$response.body?.["@odata.nextLink"] ? undefined : $request.qs?.$select }}', + }, + }, + }, + }, + }, + }, + type: 'boolean', + }, + { + displayName: 'Limit', + name: 'limit', + default: 50, + description: 'Max number of results to return', + displayOptions: { + show: { + returnAll: [false], + }, + }, + routing: { + send: { + property: '$top', + type: 'query', + value: '={{ $value }}', + }, + }, + type: 'number', + typeOptions: { + minValue: 1, + }, + validateType: 'number', + }, + { + displayName: 'Options', + name: 'options', + default: {}, + options: [ + { + displayName: 'Fields', + name: 'fields', + default: [], + description: 'The fields you want to include in the output', + displayOptions: { + hide: { + '/simplify': [true], + }, + }, + options: [ + { + name: 'Content Type', + value: 'contentType', + }, + { + name: 'Created At', + value: 'createdDateTime', + }, + { + name: 'Created By', + value: 'createdBy', + }, + { + name: 'Fields', + value: 'fields', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Last Modified At', + value: 'lastModifiedDateTime', + }, + { + name: 'Last Modified By', + value: 'lastModifiedBy', + }, + + { + name: 'Parent Reference', + value: 'parentReference', + }, + { + name: 'Web URL', + value: 'webUrl', + }, + ], + routing: { + send: { + preSend: [itemGetAllFieldsPreSend], + }, + }, + type: 'multiOptions', + }, + ], + placeholder: 'Add option', + type: 'collection', + }, + { + displayName: 'Simplify', + name: 'simplify', + default: true, + routing: { + send: { + preSend: [ + async function ( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, + ): Promise { + const simplify = this.getNodeParameter('simplify', false) as boolean; + if (simplify) { + requestOptions.qs ??= {}; + requestOptions.qs.$select = 'id,createdDateTime,lastModifiedDateTime,webUrl'; + requestOptions.qs.$expand = 'fields(select=Title)'; + } + return requestOptions; + }, + ], + }, + }, + type: 'boolean', + }, +]; + +const displayOptions = { + show: { + resource: ['item'], + operation: ['getAll'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/update.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/update.operation.ts new file mode 100644 index 0000000000..dd4aa9c51d --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/update.operation.ts @@ -0,0 +1,77 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { itemColumnsPreSend } from '../../helpers/utils'; +import { listRLC, siteRLC, untilListSelected, untilSiteSelected } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to update an item in', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + displayName: + 'Due to API restrictions, the following column types cannot be updated: Hyperlink, Location, Metadata', + name: 'noticeUnsupportedFields', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + type: 'notice', + default: '', + }, + { + displayName: 'Columns', + name: 'columns', + default: { + mappingMode: 'defineBelow', + value: null, + }, + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + noDataExpression: true, + required: true, + routing: { + send: { + preSend: [itemColumnsPreSend], + }, + }, + type: 'resourceMapper', + typeOptions: { + loadOptionsDependsOn: ['site.value', 'list.value'], + resourceMapper: { + resourceMapperMethod: 'getMappingColumns', + mode: 'update', + fieldWords: { + singular: 'column', + plural: 'columns', + }, + addAllFields: true, + multiKeyMatch: false, + }, + }, + }, +]; + +const displayOptions = { + show: { + resource: ['item'], + operation: ['update'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/upsert.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/upsert.operation.ts new file mode 100644 index 0000000000..d72905e22b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/item/upsert.operation.ts @@ -0,0 +1,77 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { itemColumnsPreSend } from '../../helpers/utils'; +import { listRLC, siteRLC, untilListSelected, untilSiteSelected } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to create or update an item in', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + displayName: + 'Due to API restrictions, the following column types cannot be updated: Hyperlink, Location, Metadata', + name: 'noticeUnsupportedFields', + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + type: 'notice', + default: '', + }, + { + displayName: 'Columns', + name: 'columns', + default: { + mappingMode: 'defineBelow', + value: null, + }, + displayOptions: { + hide: { + ...untilSiteSelected, + ...untilListSelected, + }, + }, + noDataExpression: true, + required: true, + routing: { + send: { + preSend: [itemColumnsPreSend], + }, + }, + type: 'resourceMapper', + typeOptions: { + loadOptionsDependsOn: ['site.value', 'list.value'], + resourceMapper: { + resourceMapperMethod: 'getMappingColumns', + mode: 'upsert', + fieldWords: { + singular: 'column', + plural: 'columns', + }, + addAllFields: true, + multiKeyMatch: false, + }, + }, + }, +]; + +const displayOptions = { + show: { + resource: ['item'], + operation: ['upsert'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/List.resource.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/List.resource.ts new file mode 100644 index 0000000000..bcd00fb8fa --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/List.resource.ts @@ -0,0 +1,65 @@ +import type { INodeProperties } from 'n8n-workflow'; + +import * as get from './get.operation'; +import * as getAll from './getAll.operation'; +import { handleErrorPostReceive, simplifyListPostReceive } from '../../helpers/utils'; + +export const description: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['list'], + }, + }, + options: [ + { + name: 'Get', + value: 'get', + description: 'Retrieve details of a single list', + routing: { + request: { + ignoreHttpStatusErrors: true, + method: 'GET', + url: '=/sites/{{ $parameter["site"] }}/lists/{{ $parameter["list"] }}', + }, + output: { + postReceive: [handleErrorPostReceive, simplifyListPostReceive], + }, + }, + action: 'Get list', + }, + { + name: 'Get Many', + value: 'getAll', + description: 'Retrieve a list of', + routing: { + request: { + method: 'GET', + url: '=/sites/{{ $parameter["site"] }}/lists', + }, + output: { + postReceive: [ + handleErrorPostReceive, + { + type: 'rootProperty', + properties: { + property: 'value', + }, + }, + simplifyListPostReceive, + ], + }, + }, + action: 'Get many lists', + }, + ], + default: 'getAll', + }, + + ...get.description, + ...getAll.description, +]; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/get.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/get.operation.ts new file mode 100644 index 0000000000..4a58cf182b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/get.operation.ts @@ -0,0 +1,42 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { listRLC, siteRLC, untilSiteSelected } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + ...listRLC, + description: 'Select the list you want to retrieve', + displayOptions: { + hide: { + ...untilSiteSelected, + }, + }, + }, + { + displayName: 'Simplify', + name: 'simplify', + default: true, + routing: { + send: { + property: '$select', + type: 'query', + value: + '={{ $value ? "id,name,displayName,description,createdDateTime,lastModifiedDateTime,webUrl" : undefined }}', + }, + }, + type: 'boolean', + }, +]; + +const displayOptions = { + show: { + resource: ['list'], + operation: ['get'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/getAll.operation.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/getAll.operation.ts new file mode 100644 index 0000000000..603ad94a33 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/descriptions/list/getAll.operation.ts @@ -0,0 +1,83 @@ +import { updateDisplayOptions, type INodeProperties } from 'n8n-workflow'; + +import { siteRLC } from '../common.descriptions'; + +const properties: INodeProperties[] = [ + { + ...siteRLC, + description: 'Select the site to retrieve lists from', + }, + { + displayName: 'Return All', + name: 'returnAll', + default: false, + description: 'Whether to return all results or only up to a given limit', + routing: { + send: { + paginate: '={{ $value }}', + }, + operations: { + pagination: { + type: 'generic', + properties: { + continue: '={{ !!$response.body?.["@odata.nextLink"] }}', + request: { + url: '={{ $response.body?.["@odata.nextLink"] ?? $request.url }}', + qs: { + $select: + '={{ !!$response.body?.["@odata.nextLink"] ? undefined : $request.qs?.$select }}', + }, + }, + }, + }, + }, + }, + type: 'boolean', + }, + { + displayName: 'Limit', + name: 'limit', + default: 50, + description: 'Max number of results to return', + displayOptions: { + show: { + returnAll: [false], + }, + }, + routing: { + send: { + property: '$top', + type: 'query', + value: '={{ $value }}', + }, + }, + type: 'number', + typeOptions: { + minValue: 1, + }, + validateType: 'number', + }, + { + displayName: 'Simplify', + name: 'simplify', + default: true, + routing: { + send: { + property: '$select', + type: 'query', + value: + '={{ $value ? "id,name,displayName,description,createdDateTime,lastModifiedDateTime,webUrl" : undefined }}', + }, + }, + type: 'boolean', + }, +]; + +const displayOptions = { + show: { + resource: ['list'], + operation: ['getAll'], + }, +}; + +export const description = updateDisplayOptions(displayOptions, properties); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/helpers/interfaces.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/helpers/interfaces.ts new file mode 100644 index 0000000000..21baf94f8b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/helpers/interfaces.ts @@ -0,0 +1,46 @@ +import type { IDataObject } from 'n8n-workflow'; + +export interface IListColumnType { + id: string; + hidden: boolean; + name: string; + displayName: string; + readOnly: boolean; + required: boolean; + type: string; + enforceUniqueValues: boolean; + choice?: { + choices: string[]; + }; +} + +export interface IDriveItem { + id: string; + name: string; + file?: IDataObject; + folder?: IDataObject; +} + +export interface IListItem { + id: string; + fields: { + Title: string; + }; +} + +export interface IList { + id: string; + displayName: string; +} + +export interface ISite { + id: string; + title: string; +} + +export interface IErrorResponse { + error: { + code: string; + message: string; + }; +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/helpers/utils.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/helpers/utils.ts new file mode 100644 index 0000000000..c30ad9549f --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/helpers/utils.ts @@ -0,0 +1,277 @@ +import type { + IDataObject, + IExecuteSingleFunctions, + IHttpRequestOptions, + IN8nHttpFullResponse, + INodeExecutionData, + JsonObject, + ResourceMapperValue, +} from 'n8n-workflow'; +import { jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow'; + +import type { IErrorResponse } from './interfaces'; +import { microsoftSharePointApiRequest } from '../transport'; + +export async function simplifyItemPostReceive( + this: IExecuteSingleFunctions, + items: INodeExecutionData[], + _response: IN8nHttpFullResponse, +): Promise { + if (items.length === 0) { + return items; + } + + const simplify = this.getNodeParameter('simplify') as boolean; + if (simplify) { + for (const item of items) { + delete item.json['@odata.context']; + delete item.json['@odata.etag']; + delete item.json['fields@odata.navigationLink']; + delete (item.json.fields as IDataObject)?.['@odata.etag']; + } + } + + return items; +} + +export async function simplifyListPostReceive( + this: IExecuteSingleFunctions, + items: INodeExecutionData[], + _response: IN8nHttpFullResponse, +): Promise { + if (items.length === 0) { + return items; + } + + const simplify = this.getNodeParameter('simplify') as boolean; + if (simplify) { + for (const item of items) { + delete item.json['@odata.context']; + delete item.json['@odata.etag']; + } + } + + return items; +} + +export async function downloadFilePostReceive( + this: IExecuteSingleFunctions, + _items: INodeExecutionData[], + response: IN8nHttpFullResponse, +): Promise { + let fileName: string | undefined; + if (response.headers['content-disposition']) { + let fileNameMatch = /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec( + response.headers['content-disposition'] as string, + ); + fileName = + fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = /filename="?([^"]*?)"?(;|$)/g.exec( + response.headers['content-disposition'] as string, + ); + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + } + + const newItem: INodeExecutionData = { + json: {}, + binary: { + data: await this.helpers.prepareBinaryData( + response.body as Buffer, + fileName, + response.headers['content-type'] as string, + ), + }, + }; + + return [newItem]; +} + +export async function uploadFilePreSend( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, +): Promise { + const binaryProperty = this.getNodeParameter('fileContents') as string; + this.helpers.assertBinaryData(binaryProperty); + const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(binaryProperty); + requestOptions.body = binaryDataBuffer; + return requestOptions; +} + +export async function itemGetAllFieldsPreSend( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, +): Promise { + const fields = this.getNodeParameter('options.fields') as string[]; + requestOptions.qs ??= {}; + if (fields.some((x) => x === 'fields')) { + requestOptions.qs.$expand = 'fields'; + } + requestOptions.qs.$select = fields.map((x) => x); + return requestOptions; +} + +export async function itemColumnsPreSend( + this: IExecuteSingleFunctions, + requestOptions: IHttpRequestOptions, +): Promise { + const mapperValue = this.getNodeParameter('columns') as ResourceMapperValue; + const operation = this.getNodeParameter('operation') as string; + + if (['upsert', 'update'].includes(operation) && mapperValue.matchingColumns?.length > 0) { + if (!mapperValue.matchingColumns.includes('id')) { + const site = this.getNodeParameter('site', undefined, { extractValue: true }) as string; + const list = this.getNodeParameter('list', undefined, { extractValue: true }) as string; + + const response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/lists/${list}/items`, + {}, + { + $filter: mapperValue.matchingColumns + .map((x) => `fields/${x} eq '${mapperValue.value![x]}'`) + .join(' and'), + }, + { + Prefer: 'HonorNonIndexedQueriesWarningMayFailRandomly', + }, + ); + if (response.value?.length === 1) { + mapperValue.matchingColumns.push('id'); + mapperValue.value ??= {}; + mapperValue.value.id = response.value[0].id; + } + } + + if (operation === 'upsert') { + if (mapperValue.matchingColumns.includes('id')) { + if (!mapperValue.value?.id) { + throw new NodeOperationError( + this.getNode(), + "The column(s) don't match any existing item", + { + description: 'Double-check the value(s) for the columns to match and try again', + }, + ); + } + requestOptions.url += '/' + mapperValue.value.id; + delete mapperValue.value.id; + requestOptions.method = 'PATCH'; + } + } else if (operation === 'update') { + if (mapperValue.matchingColumns.includes('id') && mapperValue.value?.id) { + requestOptions.url += '/' + mapperValue.value.id; + delete mapperValue.value.id; + } else { + throw new NodeOperationError( + this.getNode(), + "The column(s) don't match any existing item", + { + description: 'Double-check the value(s) for the columns to match and try again', + }, + ); + } + } + } + + const fields = {} as IDataObject; + for (const [key, value] of Object.entries(mapperValue.value ?? {})) { + if (mapperValue.schema.find((x) => x.id === key)?.type === 'url') { + fields[key] = { + Description: value, + Url: value, + }; + } else { + fields[key] = value; + } + } + requestOptions.body ??= {}; + (requestOptions.body as IDataObject).fields = fields; + + return requestOptions; +} + +export async function handleErrorPostReceive( + this: IExecuteSingleFunctions, + data: INodeExecutionData[], + response: IN8nHttpFullResponse, +): Promise { + if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) { + const resource = this.getNodeParameter('resource') as string; + const operation = this.getNodeParameter('operation') as string; + + if (resource === 'file' && operation === 'download' && Buffer.isBuffer(response.body)) { + response.body = jsonParse((response.body as Buffer).toString()); + } + const error = (response.body as IErrorResponse)?.error ?? {}; + + if (resource === 'file') { + if (operation === 'download') { + } else if (operation === 'update') { + } else if (operation === 'upload') { + } + } else if (resource === 'item') { + if (operation === 'create') { + if (error.code === 'invalidRequest') { + if ( + error.message === + 'One or more fields with unique constraints already has the provided value.' + ) { + throw new NodeApiError(this.getNode(), response as unknown as JsonObject, { + message: 'One or more fields with unique constraints already has the provided value', + description: "Double-check the value(s) in 'Values to Send' and try again", + }); + } else { + throw new NodeApiError(this.getNode(), response as unknown as JsonObject, { + message: error.message, + description: "Double-check the value(s) in 'Values to Send' and try again", + }); + } + } + } else if (operation === 'delete') { + } else if (operation === 'get') { + } else if (operation === 'getAll') { + } else if (operation === 'update') { + if (error.code === 'invalidRequest') { + throw new NodeApiError(this.getNode(), response as unknown as JsonObject, { + message: error.message, + description: "Double-check the value(s) in 'Values to Update' and try again", + }); + } + } else if (operation === 'upsert') { + if (error.code === 'invalidRequest') { + throw new NodeApiError(this.getNode(), response as unknown as JsonObject, { + message: error.message, + description: "Double-check the value(s) in 'Values to Send' and try again", + }); + } + } + } else if (resource === 'list') { + if (operation === 'get') { + } else if (operation === 'getAll') { + } + } + + if (error.code === 'itemNotFound') { + if (error.message.includes('list item')) { + throw new NodeApiError(this.getNode(), response as unknown as JsonObject, { + message: "The required item doesn't match any existing one", + description: "Double-check the value in the parameter 'Item' and try again", + }); + } else if (error.message.includes('list')) { + throw new NodeApiError(this.getNode(), response as unknown as JsonObject, { + message: "The required list doesn't match any existing one", + description: "Double-check the value in the parameter 'List' and try again", + }); + } + } + + throw new NodeApiError(this.getNode(), response as unknown as JsonObject); + } + + return data; +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/methods/index.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/methods/index.ts new file mode 100644 index 0000000000..eef4c5ef76 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/methods/index.ts @@ -0,0 +1,2 @@ +export * as listSearch from './listSearch'; +export * as resourceMapping from './resourceMapping'; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/methods/listSearch.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/methods/listSearch.ts new file mode 100644 index 0000000000..392071ec5f --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/methods/listSearch.ts @@ -0,0 +1,252 @@ +import type { + IDataObject, + ILoadOptionsFunctions, + INodeListSearchItems, + INodeListSearchResult, +} from 'n8n-workflow'; + +import type { IDriveItem, IList, IListItem, ISite } from '../helpers/interfaces'; +import { microsoftSharePointApiRequest } from '../transport'; + +export async function getFiles( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, +): Promise { + const site = this.getNodeParameter('site', undefined, { extractValue: true }) as string; + const folder = this.getNodeParameter('folder', undefined, { extractValue: true }) as string; + + let response: any; + if (paginationToken) { + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/drive/items/${folder}/children`, + {}, + undefined, + undefined, + paginationToken, + ); + } else { + // File filter not supported + // https://learn.microsoft.com/en-us/onedrive/developer/rest-api/concepts/filtering-results?view=odsp-graph-online#filterable-properties + const qs: IDataObject = { + $select: 'id,name,file', + }; + if (filter) { + qs.$filter = `name eq '${filter}'`; + } + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/drive/items/${folder}/children`, + {}, + qs, + ); + } + + const items: IDriveItem[] = response.value; + + const results: INodeListSearchItems[] = items + .filter((x) => x.file) + .map((g) => ({ + name: g.name, + value: g.id, + })) + .sort((a, b) => + a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }), + ); + + return { results, paginationToken: response['@odata.nextLink'] }; +} + +export async function getFolders( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, +): Promise { + const site = this.getNodeParameter('site', undefined, { extractValue: true }) as string; + + let response: any; + if (paginationToken) { + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/drive/items`, + {}, + undefined, + undefined, + paginationToken, + ); + } else { + const qs: IDataObject = { + $select: 'id,name,folder', + // Folder filter not supported, but filter is still required + // https://learn.microsoft.com/en-us/onedrive/developer/rest-api/concepts/filtering-results?view=odsp-graph-online#filterable-properties + $filter: 'folder ne null', + }; + if (filter) { + qs.$filter = `name eq '${filter}'`; + } + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/drive/items`, + {}, + qs, + ); + } + + const items: IDriveItem[] = response.value; + + const results: INodeListSearchItems[] = items + .filter((x) => x.folder) + .map((g) => ({ + name: g.name, + value: g.id, + })) + .sort((a, b) => + a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }), + ); + + return { results, paginationToken: response['@odata.nextLink'] }; +} + +export async function getItems( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, +): Promise { + const site = this.getNodeParameter('site', undefined, { extractValue: true }) as string; + const list = this.getNodeParameter('list', undefined, { extractValue: true }) as string; + + let response: any; + if (paginationToken) { + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/lists/${list}/items`, + {}, + undefined, + undefined, + paginationToken, + ); + } else { + const qs: IDataObject = { + $expand: 'fields(select=Title)', + $select: 'id,fields', + }; + if (filter) { + qs.$filter = `fields/Title eq '${filter}'`; + } + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/lists/${list}/items`, + {}, + qs, + ); + } + + const items: IListItem[] = response.value; + + const results: INodeListSearchItems[] = items + .map((g) => ({ + name: g.fields.Title ?? g.id, + value: g.id, + })) + .sort((a, b) => + a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }), + ); + + return { results, paginationToken: response['@odata.nextLink'] }; +} + +export async function getLists( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, +): Promise { + const site = this.getNodeParameter('site', undefined, { extractValue: true }) as string; + + let response: any; + if (paginationToken) { + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/lists`, + {}, + undefined, + undefined, + paginationToken, + ); + } else { + const qs: IDataObject = { + $select: 'id,displayName', + }; + if (filter) { + qs.$filter = `displayName eq '${filter}'`; + } + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/lists`, + {}, + qs, + ); + } + + const lists: IList[] = response.value; + + const results: INodeListSearchItems[] = lists + .map((g) => ({ + name: g.displayName, + value: g.id, + })) + .sort((a, b) => + a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }), + ); + + return { results, paginationToken: response['@odata.nextLink'] }; +} + +export async function getSites( + this: ILoadOptionsFunctions, + filter?: string, + paginationToken?: string, +): Promise { + let response: any; + if (paginationToken) { + response = await microsoftSharePointApiRequest.call( + this, + 'GET', + '/sites', + {}, + undefined, + undefined, + paginationToken, + ); + } else { + const qs: IDataObject = { + $select: 'id,title', + $search: '*', + }; + if (filter) { + qs.$search = filter; + } + response = await microsoftSharePointApiRequest.call(this, 'GET', '/sites', {}, qs); + } + + const sites: ISite[] = response.value; + + const results: INodeListSearchItems[] = sites + .map((g) => ({ + name: g.title, + value: g.id, + })) + .sort((a, b) => + a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }), + ); + + return { results, paginationToken: response['@odata.nextLink'] }; +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/methods/resourceMapping.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/methods/resourceMapping.ts new file mode 100644 index 0000000000..306a04608b --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/methods/resourceMapping.ts @@ -0,0 +1,95 @@ +import type { + FieldType, + ILoadOptionsFunctions, + ResourceMapperField, + ResourceMapperFields, +} from 'n8n-workflow'; + +import type { IListColumnType } from '../helpers/interfaces'; +import { microsoftSharePointApiRequest } from '../transport'; + +const unsupportedFields = ['geoLocation', 'location', 'term', 'url']; +const fieldTypeMapping: Partial> = { + string: ['text', 'user', 'lookup'], + // unknownFutureValue: rating + number: ['number', 'currency', 'unknownFutureValue'], + boolean: ['boolean'], + dateTime: ['dateTime'], + object: ['thumbnail'], + options: ['choice'], +}; + +function mapType(column: IListColumnType): FieldType | undefined { + if (unsupportedFields.includes(column.type)) { + return undefined; + } + let mappedType: FieldType = 'string'; + for (const t of Object.keys(fieldTypeMapping)) { + const postgresTypes = fieldTypeMapping[t as FieldType]; + if (postgresTypes?.includes(column.type)) { + mappedType = t as FieldType; + } + } + return mappedType; +} + +export async function getMappingColumns( + this: ILoadOptionsFunctions, +): Promise { + const site = this.getNodeParameter('site', undefined, { extractValue: true }) as string; + const list = this.getNodeParameter('list', undefined, { extractValue: true }) as string; + const operation = this.getNodeParameter('operation') as string; + + const response = await microsoftSharePointApiRequest.call( + this, + 'GET', + `/sites/${site}/lists/${list}/contentTypes`, + {}, + { expand: 'columns' }, + ); + + const columns: IListColumnType[] = response.value[0].columns; + + const fields: ResourceMapperField[] = []; + + for (const column of columns.filter((x) => !x.hidden && !x.readOnly)) { + const fieldType = mapType(column); + const field = { + id: column.name, + canBeUsedToMatch: column.enforceUniqueValues && column.required, + defaultMatch: false, + display: true, + displayName: column.displayName, + readOnly: column.readOnly || !fieldType, + required: column.required, + type: fieldType, + } as ResourceMapperField; + if (field.type === 'options') { + field.options = []; + if (Array.isArray(column.choice?.choices)) { + for (const choice of column.choice.choices) { + field.options.push({ + name: choice, + value: choice, + }); + } + } + } + fields.push(field); + } + + if (operation === 'update') { + fields.push({ + id: 'id', + canBeUsedToMatch: true, + defaultMatch: false, + display: true, + displayName: 'ID', + readOnly: true, + required: true, + type: 'string', + }); + } + + return { fields }; +} diff --git a/packages/nodes-base/credentials/icons/SharePoint.svg b/packages/nodes-base/nodes/Microsoft/SharePoint/microsoftSharePoint.svg similarity index 100% rename from packages/nodes-base/credentials/icons/SharePoint.svg rename to packages/nodes-base/nodes/Microsoft/SharePoint/microsoftSharePoint.svg diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials.ts new file mode 100644 index 0000000000..c1839ce40c --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials.ts @@ -0,0 +1,27 @@ +export const credentials = { + microsoftSharePointOAuth2Api: { + grantType: 'authorizationCode', + authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize', + accessTokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + scope: 'openid offline_access https://mydomain.sharepoint.com/.default', + authQueryParameters: 'response_mode=query', + authentication: 'body', + oauthTokenData: { + token_type: 'Bearer', + scope: + 'https://mydomain.sharepoint.com/Sites.Manage.All https://mydomain.sharepoint.com/Sites.Read.All https://mydomain.sharepoint.com/Sites.ReadWrite.All https://mydomain.sharepoint.com/Sites.Selected https://mydomain.sharepoint.com/User.Read https://mydomain.sharepoint.com/.default', + expires_in: 4763, + ext_expires_in: 4763, + access_token: 'ACCESSTOKEN', + refresh_token: 'REFRESHTOKEN', + id_token: 'IDTOKEN', + callbackQueryString: { + session_state: 'SESSIONSTATE', + }, + }, + subdomain: 'mydomain', + baseUrl: 'https://mydomain.sharepoint.com/_api/v2.0', + }, +}; diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials/oauth2.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials/oauth2.test.ts new file mode 100644 index 0000000000..4a8020b7be --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials/oauth2.test.ts @@ -0,0 +1,36 @@ +import { CredentialsHelper } from '@nodes-testing/credentials-helper'; +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + jest.spyOn(CredentialsHelper.prototype, 'getParentTypes').mockReturnValueOnce(['oAuth2Api']); + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['oauth2.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists/list1?%24select=id%2Cname%2CdisplayName%2Cdescription%2CcreatedDateTime%2ClastModifiedDateTime%2CwebUrl', + statusCode: 200, + requestHeaders: { Authorization: 'Bearer ACCESSTOKEN' }, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#lists/$entity', + '@odata.etag': '"58a279af-1f06-4392-a5ed-2b37fa1d6c1d,5"', + createdDateTime: '2025-03-12T19:38:40Z', + description: 'My List 1', + id: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + lastModifiedDateTime: '2025-03-12T22:18:18Z', + name: 'list1', + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list', + displayName: 'list1', + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials/oauth2.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials/oauth2.workflow.json new file mode 100644 index 0000000000..51c49d40cd --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/credentials/oauth2.workflow.json @@ -0,0 +1,70 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "list", + "operation": "get", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "createdDateTime": "2025-03-12T19:38:40Z", + "description": "My List 1", + "id": "58a279af-1f06-4392-a5ed-2b37fa1d6c1d", + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "name": "list1", + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list", + "displayName": "list1" + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/download.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/download.test.ts new file mode 100644 index 0000000000..a779a02bfe --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/download.test.ts @@ -0,0 +1,28 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['download.workflow.json'], + assertBinaryData: true, + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/drive/items/item1/content', + statusCode: 200, + responseBody: {}, + responseHeaders: { + 'content-type': 'application/json', + 'content-disposition': + "attachment; filename*=UTF-8''weird%20file%20%E2%82%AC%20name.json", + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/download.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/download.workflow.json new file mode 100644 index 0000000000..5772acd608 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/download.workflow.json @@ -0,0 +1,76 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "37ab6212-d506-4b53-b63e-14b6a2a05907", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "folder": { + "__rl": true, + "value": "folder1", + "mode": "list", + "cachedResultName": "folder1" + }, + "file": { + "__rl": true, + "value": "item1", + "mode": "list", + "cachedResultName": "file.json" + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [220, 0], + "id": "f9037af8-4f52-4f9f-b8ae-de860f890792", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "binary": { + "data": { + "data": "e30=", + "fileExtension": "json", + "fileSize": "2 B", + "fileType": "json", + "mimeType": "application/json", + "fileName": "weird file € name.json" + } + }, + "json": {} + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/update.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/update.test.ts new file mode 100644 index 0000000000..f4498e1378 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/update.test.ts @@ -0,0 +1,148 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['update.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'patch', + path: '/sites/site1/drive/items/item1', + statusCode: 200, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#items/$entity', + '@content.downloadUrl': + 'https://mydomain.sharepoint.com/sites/site1/_layouts/15/download.aspx?UniqueId=d86f89ab-378e-43bc-8d46-18d2f52bd603', + createdBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + displayName: 'John Doe', + }, + }, + createdDateTime: '2025-03-13T19:23:53Z', + eTag: '"{D86F89AB-378E-43BC-8D46-18D2F52BD603},3"', + id: '01SPEVVYFLRFX5RDRXXRBY2RQY2L2SXVQD', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + displayName: 'John Doe', + }, + }, + lastModifiedDateTime: '2025-03-24T19:48:25Z', + name: 'file2.json', + parentReference: { + driveType: 'documentLibrary', + driveId: 'b!HXyh83lynUOhdUenWLZcmP1mvjoj7J9Gq8YzLjqVv3W_vPrVy_93T7lzoXbjRjzK', + id: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + name: 'folder1', + path: '/drive/root:/folder1', + siteId: 'f3a17c1d-7279-439d-a175-47a758b65c98', + }, + webUrl: + 'https://mydomain.sharepoint.com/sites/site1/Shared%20Documents/folder1/file2.json', + cTag: '"c:{D86F89AB-378E-43BC-8D46-18D2F52BD603},2"', + file: { + hashes: { + quickXorHash: 'jY7BcIP9Th3EZ4PMCLrv4DnQTY4=', + }, + irmEffectivelyEnabled: false, + irmEnabled: false, + mimeType: 'application/json', + }, + fileSystemInfo: { + createdDateTime: '2025-03-13T19:23:53Z', + lastModifiedDateTime: '2025-03-24T19:48:25Z', + }, + shared: { + effectiveRoles: ['write'], + scope: 'users', + }, + size: 37, + }, + }, + { + method: 'put', + path: '/sites/site1/drive/items/item1/content', + statusCode: 200, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#items/$entity', + '@content.downloadUrl': + 'https://mydomain.sharepoint.com/sites/site1/_layouts/15/download.aspx?UniqueId=d86f89ab-378e-43bc-8d46-18d2f52bd603', + createdBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + displayName: 'John Doe', + }, + }, + createdDateTime: '2025-03-13T19:23:53Z', + eTag: '"{D86F89AB-378E-43BC-8D46-18D2F52BD603},3"', + id: '01SPEVVYFLRFX5RDRXXRBY2RQY2L2SXVQD', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + displayName: 'John Doe', + }, + }, + lastModifiedDateTime: '2025-03-24T19:48:35Z', + name: 'file2.json', + parentReference: { + driveType: 'documentLibrary', + driveId: 'b!HXyh83lynUOhdUenWLZcmP1mvjoj7J9Gq8YzLjqVv3W_vPrVy_93T7lzoXbjRjzK', + id: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + name: 'folder1', + path: '/drive/root:/folder1', + siteId: 'f3a17c1d-7279-439d-a175-47a758b65c98', + }, + webUrl: + 'https://mydomain.sharepoint.com/sites/site1/Shared%20Documents/folder1/file2.json', + cTag: '"c:{D86F89AB-378E-43BC-8D46-18D2F52BD603},2"', + file: { + hashes: { + quickXorHash: 'jY7BcIP9Th3EZ4PMCLrv4DnQTY4=', + }, + irmEffectivelyEnabled: false, + irmEnabled: false, + mimeType: 'application/json', + }, + fileSystemInfo: { + createdDateTime: '2025-03-13T19:23:53Z', + lastModifiedDateTime: '2025-03-24T19:48:25Z', + }, + shared: { + effectiveRoles: ['write'], + scope: 'users', + }, + size: 37, + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/update.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/update.workflow.json new file mode 100644 index 0000000000..b5a54455d7 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/update.workflow.json @@ -0,0 +1,175 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "37ab6212-d506-4b53-b63e-14b6a2a05907", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "operation": "update", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "folder": { + "__rl": true, + "value": "folder1", + "mode": "list", + "cachedResultName": "folder1" + }, + "file": { + "__rl": true, + "value": "item1", + "mode": "list", + "cachedResultName": "file1.json" + }, + "fileName": "file2.json", + "changeFileContent": true, + "fileContents": "data", + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [660, 0], + "id": "f9037af8-4f52-4f9f-b8ae-de860f890792", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + }, + { + "parameters": { + "mode": "jsonToBinary", + "convertAllData": false, + "options": { + "useRawData": true + } + }, + "name": "Move Binary Data", + "type": "n8n-nodes-base.moveBinaryData", + "typeVersion": 1, + "position": [440, 0], + "id": "9cba2be3-50e0-4ae2-b7a6-2f0caefb50ef" + }, + { + "parameters": { + "mode": "raw", + "jsonOutput": "{\n \"data\": {\n \"my_field_1\": \"value\",\n \"my_field_2\": 1\n }\n}\n", + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [220, 0], + "id": "fdafc0d5-58dd-4f9e-a76b-0a3cb0e9a794", + "name": "Edit Fields" + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Move Binary Data": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Move Binary Data", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "@content.downloadUrl": "https://mydomain.sharepoint.com/sites/site1/_layouts/15/download.aspx?UniqueId=d86f89ab-378e-43bc-8d46-18d2f52bd603", + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#items/$entity", + "cTag": "\"c:{D86F89AB-378E-43BC-8D46-18D2F52BD603},2\"", + "createdBy": { + "application": { + "displayName": "sharepoint-n8n-test", + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-13T19:23:53Z", + "eTag": "\"{D86F89AB-378E-43BC-8D46-18D2F52BD603},3\"", + "file": { + "hashes": { + "quickXorHash": "jY7BcIP9Th3EZ4PMCLrv4DnQTY4=" + }, + "irmEffectivelyEnabled": false, + "irmEnabled": false, + "mimeType": "application/json" + }, + "fileSystemInfo": { + "createdDateTime": "2025-03-13T19:23:53Z", + "lastModifiedDateTime": "2025-03-24T19:48:25Z" + }, + "id": "01SPEVVYFLRFX5RDRXXRBY2RQY2L2SXVQD", + "lastModifiedBy": { + "application": { + "displayName": "sharepoint-n8n-test", + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-24T19:48:35Z", + "name": "file2.json", + "parentReference": { + "driveId": "b!HXyh83lynUOhdUenWLZcmP1mvjoj7J9Gq8YzLjqVv3W_vPrVy_93T7lzoXbjRjzK", + "driveType": "documentLibrary", + "id": "01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA", + "name": "folder1", + "path": "/drive/root:/folder1", + "siteId": "f3a17c1d-7279-439d-a175-47a758b65c98" + }, + "shared": { + "effectiveRoles": ["write"], + "scope": "users" + }, + "size": 37, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Shared%20Documents/folder1/file2.json" + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/upload.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/upload.test.ts new file mode 100644 index 0000000000..55df0a3e40 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/upload.test.ts @@ -0,0 +1,82 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['upload.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'put', + path: '/sites/site1/drive/items/folder1:/file1.json:/content', + statusCode: 201, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#items/$entity', + '@content.downloadUrl': + 'https://mydomain.sharepoint.com/sites/site1/_layouts/15/download.aspx?UniqueId=d86f89ab-378e-43bc-8d46-18d2f52bd603', + createdBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + displayName: 'John Doe', + }, + }, + createdDateTime: '2025-03-13T19:23:53Z', + eTag: '"{D86F89AB-378E-43BC-8D46-18D2F52BD603},3"', + id: '01SPEVVYFLRFX5RDRXXRBY2RQY2L2SXVQD', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + displayName: 'John Doe', + }, + }, + lastModifiedDateTime: '2025-03-24T19:48:25Z', + name: 'file2.json', + parentReference: { + driveType: 'documentLibrary', + driveId: 'b!HXyh83lynUOhdUenWLZcmP1mvjoj7J9Gq8YzLjqVv3W_vPrVy_93T7lzoXbjRjzK', + id: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + name: 'folder1', + path: '/drive/root:/folder1', + siteId: 'f3a17c1d-7279-439d-a175-47a758b65c98', + }, + webUrl: + 'https://mydomain.sharepoint.com/sites/site1/Shared%20Documents/folder1/file1.json', + cTag: '"c:{D86F89AB-378E-43BC-8D46-18D2F52BD603},2"', + file: { + hashes: { + quickXorHash: 'jY7BcIP9Th3EZ4PMCLrv4DnQTY4=', + }, + irmEffectivelyEnabled: false, + irmEnabled: false, + mimeType: 'application/json', + }, + fileSystemInfo: { + createdDateTime: '2025-03-13T19:23:53Z', + lastModifiedDateTime: '2025-03-24T19:48:25Z', + }, + shared: { + effectiveRoles: ['write'], + scope: 'users', + }, + size: 37, + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/upload.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/upload.workflow.json new file mode 100644 index 0000000000..a28daf57d7 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/file/upload.workflow.json @@ -0,0 +1,168 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "37ab6212-d506-4b53-b63e-14b6a2a05907", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "operation": "upload", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "folder": { + "__rl": true, + "value": "folder1", + "mode": "list", + "cachedResultName": "folder1" + }, + "fileName": "file1.json", + "fileContents": "data", + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [660, 0], + "id": "f9037af8-4f52-4f9f-b8ae-de860f890792", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + }, + { + "parameters": { + "mode": "jsonToBinary", + "convertAllData": false, + "options": { + "useRawData": true + } + }, + "name": "Move Binary Data", + "type": "n8n-nodes-base.moveBinaryData", + "typeVersion": 1, + "position": [440, 0], + "id": "9cba2be3-50e0-4ae2-b7a6-2f0caefb50ef" + }, + { + "parameters": { + "mode": "raw", + "jsonOutput": "{\n \"data\": {\n \"my_field_1\": \"value\",\n \"my_field_2\": 1\n }\n}\n", + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [220, 0], + "id": "fdafc0d5-58dd-4f9e-a76b-0a3cb0e9a794", + "name": "Edit Fields" + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Edit Fields", + "type": "main", + "index": 0 + } + ] + ] + }, + "Move Binary Data": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + }, + "Edit Fields": { + "main": [ + [ + { + "node": "Move Binary Data", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "@content.downloadUrl": "https://mydomain.sharepoint.com/sites/site1/_layouts/15/download.aspx?UniqueId=d86f89ab-378e-43bc-8d46-18d2f52bd603", + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#items/$entity", + "cTag": "\"c:{D86F89AB-378E-43BC-8D46-18D2F52BD603},2\"", + "createdBy": { + "application": { + "displayName": "sharepoint-n8n-test", + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-13T19:23:53Z", + "eTag": "\"{D86F89AB-378E-43BC-8D46-18D2F52BD603},3\"", + "file": { + "hashes": { + "quickXorHash": "jY7BcIP9Th3EZ4PMCLrv4DnQTY4=" + }, + "irmEffectivelyEnabled": false, + "irmEnabled": false, + "mimeType": "application/json" + }, + "fileSystemInfo": { + "createdDateTime": "2025-03-13T19:23:53Z", + "lastModifiedDateTime": "2025-03-24T19:48:25Z" + }, + "id": "01SPEVVYFLRFX5RDRXXRBY2RQY2L2SXVQD", + "lastModifiedBy": { + "application": { + "displayName": "sharepoint-n8n-test", + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-24T19:48:25Z", + "name": "file2.json", + "parentReference": { + "driveId": "b!HXyh83lynUOhdUenWLZcmP1mvjoj7J9Gq8YzLjqVv3W_vPrVy_93T7lzoXbjRjzK", + "driveType": "documentLibrary", + "id": "01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA", + "name": "folder1", + "path": "/drive/root:/folder1", + "siteId": "f3a17c1d-7279-439d-a175-47a758b65c98" + }, + "shared": { + "effectiveRoles": ["write"], + "scope": "users" + }, + "size": 37, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Shared%20Documents/folder1/file1.json" + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/helpers/utils.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/helpers/utils.test.ts new file mode 100644 index 0000000000..b780a2f319 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/helpers/utils.test.ts @@ -0,0 +1,54 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { IBinaryData, IExecuteSingleFunctions } from 'n8n-workflow'; + +import { downloadFilePostReceive } from '../../helpers/utils'; + +describe('Microsoft SharePoint Node', () => { + let executeSingleFunctions: MockProxy; + + beforeEach(() => { + executeSingleFunctions = mock(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should download file post receive', async () => { + const mockResponse = { + body: '', + statusCode: 200, + headers: { + 'content-disposition': "attachment; filename*=UTF-8''encoded%20name.pdf", + 'content-type': 'application/pdf', + }, + }; + const mockPrepareBinaryData = jest.fn().mockReturnValueOnce({ + data: '', + mimeType: 'application/pdf', + fileName: 'encoded name.pdf', + } as IBinaryData); + executeSingleFunctions.helpers.prepareBinaryData = mockPrepareBinaryData; + + const result = await downloadFilePostReceive.call(executeSingleFunctions, [], mockResponse); + + expect(mockPrepareBinaryData).toHaveBeenCalledWith( + mockResponse.body, + 'encoded name.pdf', + 'application/pdf', + ); + expect(result).toEqual([ + { + json: {}, + binary: { + data: { + data: '', + mimeType: 'application/pdf', + fileName: 'encoded name.pdf', + }, + }, + }, + ]); + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/create.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/create.test.ts new file mode 100644 index 0000000000..33bcba46d5 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/create.test.ts @@ -0,0 +1,107 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['create.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'post', + path: '/sites/site1/lists/list1/items', + statusCode: 201, + requestBody: { + fields: { + bool: true, + Title: 'Title 1', + choice: 'Choice 1', + datetime: '2025-03-24T00:00:00', + number: 1, + currency: 1, + LikesCount: 1, + RatingCount: 1, + AverageRating: 1, + person: '1', + lookup: '1', + image: + '{"type":"thumbnail","fileName":"file.jpg","nativeFile":{},"fieldName":"Picture","serverUrl":"https://mydomain.sharepoint.com","fieldId":"image","serverRelativeUrl":"/sites/site1/SiteAssets/Lists/list1/file.jpg","id":"image"}', + }, + }, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"625d681e-2ce3-4c2b-a490-7c8404a31427,1"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-25T20:07:23Z', + eTag: '"625d681e-2ce3-4c2b-a490-7c8404a31427,1"', + id: '1', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-25T20:07:23Z', + parentReference: { + id: '0ead9135-9622-4b6d-b319-6b8810a6dc60', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/n8ntest10/Lists/test/41_.000', + contentType: { + id: '0x0100362657F7588C5C438072A77E0EF184F4000272C0D1046D984B8097B3C00D199EDE', + name: 'Item', + }, + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/1/fields', + fields: { + '@odata.etag': '"625d681e-2ce3-4c2b-a490-7c8404a31427,1"', + Title: 'Title 1', + choice: 'Choice 1', + datetime: '2025-03-24T07:00:00Z', + number: 1, + bool: true, + currency: 1, + image: + '{"type":"thumbnail","fileName":"file.jpg","nativeFile":{},"fieldName":"Picture","serverUrl":"https://mydomain.sharepoint.com","fieldId":"image","serverRelativeUrl":"/sites/site1/SiteAssets/Lists/list1/file.jpg","id":"image"}', + AverageRating: 1, + ID: 1, + ContentType: 'Item', + Modified: '2025-03-25T20:07:23Z', + Created: '2025-03-25T20:07:23Z', + AuthorLookupId: '1', + EditorLookupId: '1', + _UIVersionString: '1.0', + Attachments: false, + Edit: '', + LinkTitleNoMenu: 'Title 1', + LinkTitle: 'Title 1', + ItemChildCount: '0', + FolderChildCount: '0', + _ComplianceFlags: '', + _ComplianceTag: '', + _ComplianceTagWrittenTime: '', + _ComplianceTagUserId: '', + AppAuthorLookupId: '1', + AppEditorLookupId: '1', + }, + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/create.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/create.workflow.json new file mode 100644 index 0000000000..597a43f28e --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/create.workflow.json @@ -0,0 +1,291 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "37ab6212-d506-4b53-b63e-14b6a2a05907", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "operation": "create", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "bool": true, + "Title": "Title 1", + "choice": "Choice 1", + "datetime": "2025-03-24T00:00:00", + "number": 1, + "currency": 1, + "LikesCount": 1, + "RatingCount": 1, + "AverageRating": 1, + "person": "1", + "lookup": "1", + "image": "{\"type\":\"thumbnail\",\"fileName\":\"file.jpg\",\"nativeFile\":{},\"fieldName\":\"Picture\",\"serverUrl\":\"https://mydomain.sharepoint.com\",\"fieldId\":\"image\",\"serverRelativeUrl\":\"/sites/site1/SiteAssets/Lists/list1/file.jpg\",\"id\":\"image\"}" + }, + "matchingColumns": [], + "schema": [ + { + "id": "Title", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, + "displayName": "Title", + "readOnly": false, + "required": true, + "type": "string" + }, + { + "id": "choice", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "choice", + "readOnly": false, + "required": false, + "type": "options", + "options": [ + { + "name": "Choice 1", + "value": "Choice 1" + }, + { + "name": "Choice 2", + "value": "Choice 2" + }, + { + "name": "Choice 3", + "value": "Choice 3" + } + ] + }, + { + "id": "datetime", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "datetime", + "readOnly": false, + "required": false, + "type": "dateTime" + }, + { + "id": "person", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "person", + "readOnly": false, + "required": false, + "type": "string", + "removed": false + }, + { + "id": "number", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "number", + "readOnly": false, + "required": false, + "type": "number" + }, + { + "id": "bool", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "bool", + "readOnly": false, + "required": false, + "type": "boolean" + }, + { + "id": "currency", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "currency", + "readOnly": false, + "required": false, + "type": "number", + "removed": false + }, + { + "id": "image", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "image", + "readOnly": false, + "required": false, + "type": "object", + "removed": false + }, + { + "id": "lookup", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "lookup", + "readOnly": false, + "required": false, + "type": "string", + "removed": false + }, + { + "id": "AverageRating", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "Rating (0-5)", + "readOnly": false, + "required": false, + "type": "number", + "removed": false + }, + { + "id": "RatingCount", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "Number of Ratings", + "readOnly": false, + "required": false, + "type": "number", + "removed": false + }, + { + "id": "LikesCount", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "Number of Likes", + "readOnly": false, + "required": false, + "type": "number", + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [220, 0], + "id": "f9037af8-4f52-4f9f-b8ae-de860f890792", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity", + "@odata.etag": "\"625d681e-2ce3-4c2b-a490-7c8404a31427,1\"", + "createdBy": { + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-25T20:07:23Z", + "eTag": "\"625d681e-2ce3-4c2b-a490-7c8404a31427,1\"", + "id": "1", + "lastModifiedBy": { + "application": { + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa", + "displayName": "sharepoint-n8n-test" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-25T20:07:23Z", + "parentReference": { + "id": "0ead9135-9622-4b6d-b319-6b8810a6dc60", + "listId": "list1", + "siteId": "site1" + }, + "webUrl": "https://mydomain.sharepoint.com/sites/n8ntest10/Lists/test/41_.000", + "contentType": { + "id": "0x0100362657F7588C5C438072A77E0EF184F4000272C0D1046D984B8097B3C00D199EDE", + "name": "Item" + }, + "fields@odata.navigationLink": "sites/site1/lists/list1/items/1/fields", + "fields": { + "@odata.etag": "\"625d681e-2ce3-4c2b-a490-7c8404a31427,1\"", + "Title": "Title 1", + "choice": "Choice 1", + "datetime": "2025-03-24T07:00:00Z", + "number": 1, + "bool": true, + "currency": 1, + "image": "{\"type\":\"thumbnail\",\"fileName\":\"file.jpg\",\"nativeFile\":{},\"fieldName\":\"Picture\",\"serverUrl\":\"https://mydomain.sharepoint.com\",\"fieldId\":\"image\",\"serverRelativeUrl\":\"/sites/site1/SiteAssets/Lists/list1/file.jpg\",\"id\":\"image\"}", + "AverageRating": 1, + "ID": 1, + "ContentType": "Item", + "Modified": "2025-03-25T20:07:23Z", + "Created": "2025-03-25T20:07:23Z", + "AuthorLookupId": "1", + "EditorLookupId": "1", + "_UIVersionString": "1.0", + "Attachments": false, + "Edit": "", + "LinkTitleNoMenu": "Title 1", + "LinkTitle": "Title 1", + "ItemChildCount": "0", + "FolderChildCount": "0", + "_ComplianceFlags": "", + "_ComplianceTag": "", + "_ComplianceTagWrittenTime": "", + "_ComplianceTagUserId": "", + "AppAuthorLookupId": "1", + "AppEditorLookupId": "1" + } + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/delete.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/delete.test.ts new file mode 100644 index 0000000000..7da1567494 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/delete.test.ts @@ -0,0 +1,22 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['delete.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'delete', + path: '/sites/site1/lists/list1/items/item1', + statusCode: 204, + responseBody: {}, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/delete.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/delete.workflow.json new file mode 100644 index 0000000000..cedbad7f83 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/delete.workflow.json @@ -0,0 +1,70 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "operation": "delete", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "item": { + "__rl": true, + "value": "item1", + "mode": "list", + "cachedResultName": "item1" + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "deleted": true + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/get.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/get.test.ts new file mode 100644 index 0000000000..f5750c70d1 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/get.test.ts @@ -0,0 +1,82 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['get.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists/list1/items/item1?%24select=id%2CcreatedDateTime%2ClastModifiedDateTime%2CwebUrl&%24expand=fields%28select%3DTitle%29', + statusCode: 200, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-12T22:18:18Z', + eTag: '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + id: 'item1', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-12T22:18:18Z', + parentReference: { + id: '84070a73-ea24-463c-8eb2-0e9afa11c63f', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000', + contentType: { + id: '0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8', + name: 'Item', + }, + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + Title: 'Item 1', + ID: 'item1', + ContentType: 'Item', + Modified: '2025-03-12T22:18:18Z', + Created: '2025-03-12T22:18:18Z', + AuthorLookupId: '7', + EditorLookupId: '7', + _UIVersionString: '1.0', + Attachments: false, + Edit: '', + LinkTitleNoMenu: 'Item 1', + LinkTitle: 'Item 1', + ItemChildCount: '0', + FolderChildCount: '0', + _ComplianceFlags: '', + _ComplianceTag: '', + _ComplianceTagWrittenTime: '', + _ComplianceTagUserId: '', + AppAuthorLookupId: '5', + AppEditorLookupId: '5', + }, + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/get.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/get.workflow.json new file mode 100644 index 0000000000..6a4f6e3c41 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/get.workflow.json @@ -0,0 +1,123 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "operation": "get", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "item": { + "__rl": true, + "value": "item1", + "mode": "list", + "cachedResultName": "item1" + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "createdBy": { + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-12T22:18:18Z", + "eTag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "id": "item1", + "lastModifiedBy": { + "application": { + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa", + "displayName": "sharepoint-n8n-test" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "parentReference": { + "id": "84070a73-ea24-463c-8eb2-0e9afa11c63f", + "listId": "list1", + "siteId": "site1" + }, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000", + "contentType": { + "id": "0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8", + "name": "Item" + }, + "fields": { + "Title": "Item 1", + "ID": "item1", + "ContentType": "Item", + "Modified": "2025-03-12T22:18:18Z", + "Created": "2025-03-12T22:18:18Z", + "AuthorLookupId": "7", + "EditorLookupId": "7", + "_UIVersionString": "1.0", + "Attachments": false, + "Edit": "", + "LinkTitleNoMenu": "Item 1", + "LinkTitle": "Item 1", + "ItemChildCount": "0", + "FolderChildCount": "0", + "_ComplianceFlags": "", + "_ComplianceTag": "", + "_ComplianceTagWrittenTime": "", + "_ComplianceTagUserId": "", + "AppAuthorLookupId": "5", + "AppEditorLookupId": "5" + } + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAll.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAll.test.ts new file mode 100644 index 0000000000..3f4d03b7ea --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAll.test.ts @@ -0,0 +1,158 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['getAll.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists/list1/items?%24select%5B0%5D=contentType&%24select%5B1%5D=createdDateTime&%24select%5B2%5D=createdBy&%24select%5B3%5D=fields&%24select%5B4%5D=id&%24select%5B5%5D=lastModifiedDateTime&%24select%5B6%5D=lastModifiedBy&%24select%5B7%5D=parentReference&%24select%5B8%5D=webUrl', + statusCode: 200, + responseBody: { + value: [ + { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-12T22:18:18Z', + eTag: '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + id: 'item1', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-12T22:18:18Z', + parentReference: { + id: '84070a73-ea24-463c-8eb2-0e9afa11c63f', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000', + contentType: { + id: '0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8', + name: 'Item', + }, + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + Title: 'Item 1', + ID: 'item1', + ContentType: 'Item', + Modified: '2025-03-12T22:18:18Z', + Created: '2025-03-12T22:18:18Z', + AuthorLookupId: '7', + EditorLookupId: '7', + _UIVersionString: '1.0', + Attachments: false, + Edit: '', + LinkTitleNoMenu: 'Item 1', + LinkTitle: 'Item 1', + ItemChildCount: '0', + FolderChildCount: '0', + _ComplianceFlags: '', + _ComplianceTag: '', + _ComplianceTagWrittenTime: '', + _ComplianceTagUserId: '', + AppAuthorLookupId: '5', + AppEditorLookupId: '5', + }, + }, + ], + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27site1%27)/lists(%27list1%27)/items?%24skiptoken=UGFnZWQ9VFJVRSZwX0lEPTE&%24top=1&%24expand=fields&%24select%5b0%5d=contentType&%24select%5b1%5d=createdDateTime&%24select%5b2%5d=createdBy&%24select%5b3%5d=fields&%24select%5b4%5d=id&%24select%5b5%5d=lastModifiedDateTime&%24select%5b6%5d=lastModifiedBy&%24select%5b7%5d=parentReference&%24select%5b8%5d=webUrl', + }, + }, + { + method: 'get', + path: '/sites(%27site1%27)/lists(%27list1%27)/items?%24skiptoken=UGFnZWQ9VFJVRSZwX0lEPTE&%24top=1&%24expand=fields&%24select%5b0%5d=contentType&%24select%5b1%5d=createdDateTime&%24select%5b2%5d=createdBy&%24select%5b3%5d=fields&%24select%5b4%5d=id&%24select%5b5%5d=lastModifiedDateTime&%24select%5b6%5d=lastModifiedBy&%24select%5b7%5d=parentReference&%24select%5b8%5d=webUrl', + statusCode: 200, + responseBody: { + value: [ + { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-12T22:18:18Z', + eTag: '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + id: 'item2', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-12T22:18:18Z', + parentReference: { + id: '84070a73-ea24-463c-8eb2-0e9afa11c63f', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000', + contentType: { + id: '0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8', + name: 'Item', + }, + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + Title: 'Item 2', + ID: 'item2', + ContentType: 'Item', + Modified: '2025-03-12T22:18:18Z', + Created: '2025-03-12T22:18:18Z', + AuthorLookupId: '7', + EditorLookupId: '7', + _UIVersionString: '1.0', + Attachments: false, + Edit: '', + LinkTitleNoMenu: 'Item 2', + LinkTitle: 'Item 2', + ItemChildCount: '0', + FolderChildCount: '0', + _ComplianceFlags: '', + _ComplianceTag: '', + _ComplianceTagWrittenTime: '', + _ComplianceTagUserId: '', + AppAuthorLookupId: '5', + AppEditorLookupId: '5', + }, + }, + ], + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAll.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAll.workflow.json new file mode 100644 index 0000000000..cd118fbd4f --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAll.workflow.json @@ -0,0 +1,198 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "filter": "fields/Title eq 'item1'", + "returnAll": true, + "options": { + "fields": [ + "contentType", + "createdDateTime", + "createdBy", + "fields", + "id", + "lastModifiedDateTime", + "lastModifiedBy", + "parentReference", + "webUrl" + ] + }, + "simplify": false, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity", + "@odata.etag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "createdBy": { + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-12T22:18:18Z", + "eTag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "id": "item1", + "lastModifiedBy": { + "application": { + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa", + "displayName": "sharepoint-n8n-test" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "parentReference": { + "id": "84070a73-ea24-463c-8eb2-0e9afa11c63f", + "listId": "list1", + "siteId": "site1" + }, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000", + "contentType": { + "id": "0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8", + "name": "Item" + }, + "fields@odata.navigationLink": "sites/site1/lists/list1/items/item1/fields", + "fields": { + "@odata.etag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "Title": "Item 1", + "ID": "item1", + "ContentType": "Item", + "Modified": "2025-03-12T22:18:18Z", + "Created": "2025-03-12T22:18:18Z", + "AuthorLookupId": "7", + "EditorLookupId": "7", + "_UIVersionString": "1.0", + "Attachments": false, + "Edit": "", + "LinkTitleNoMenu": "Item 1", + "LinkTitle": "Item 1", + "ItemChildCount": "0", + "FolderChildCount": "0", + "_ComplianceFlags": "", + "_ComplianceTag": "", + "_ComplianceTagWrittenTime": "", + "_ComplianceTagUserId": "", + "AppAuthorLookupId": "5", + "AppEditorLookupId": "5" + } + } + }, + { + "json": { + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity", + "@odata.etag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "createdBy": { + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-12T22:18:18Z", + "eTag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "id": "item2", + "lastModifiedBy": { + "application": { + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa", + "displayName": "sharepoint-n8n-test" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "parentReference": { + "id": "84070a73-ea24-463c-8eb2-0e9afa11c63f", + "listId": "list1", + "siteId": "site1" + }, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000", + "contentType": { + "id": "0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8", + "name": "Item" + }, + "fields@odata.navigationLink": "sites/site1/lists/list1/items/item1/fields", + "fields": { + "@odata.etag": "\"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1\"", + "Title": "Item 2", + "ID": "item2", + "ContentType": "Item", + "Modified": "2025-03-12T22:18:18Z", + "Created": "2025-03-12T22:18:18Z", + "AuthorLookupId": "7", + "EditorLookupId": "7", + "_UIVersionString": "1.0", + "Attachments": false, + "Edit": "", + "LinkTitleNoMenu": "Item 2", + "LinkTitle": "Item 2", + "ItemChildCount": "0", + "FolderChildCount": "0", + "_ComplianceFlags": "", + "_ComplianceTag": "", + "_ComplianceTagWrittenTime": "", + "_ComplianceTagUserId": "", + "AppAuthorLookupId": "5", + "AppEditorLookupId": "5" + } + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAllLimitSimplify.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAllLimitSimplify.test.ts new file mode 100644 index 0000000000..f1e1902e06 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAllLimitSimplify.test.ts @@ -0,0 +1,55 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['getAllLimitSimplify.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists/list1/items?%24top=2&%24select=id%2CcreatedDateTime%2ClastModifiedDateTime%2CwebUrl&%24expand=fields%28select%3DTitle%29', + statusCode: 200, + responseBody: { + value: [ + { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + createdDateTime: '2025-03-12T22:18:18Z', + id: 'item1', + lastModifiedDateTime: '2025-03-12T22:18:18Z', + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000', + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + Title: 'Item 1', + }, + }, + { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + createdDateTime: '2025-03-12T22:18:18Z', + id: 'item2', + lastModifiedDateTime: '2025-03-12T22:18:18Z', + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000', + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"07bfcdd5-450d-48ce-8dc3-04f7f59edc5f,1"', + Title: 'Item 2', + }, + }, + ], + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27site1%27)/lists(%27list1%27)/items?%24skiptoken=UGFnZWQ9VFJVRSZwX0lEPTE&%24top=1&%24expand=fields&%24select%5b0%5d=contentType&%24select%5b1%5d=createdDateTime&%24select%5b2%5d=createdBy&%24select%5b3%5d=fields&%24select%5b4%5d=id&%24select%5b5%5d=lastModifiedDateTime&%24select%5b6%5d=lastModifiedBy&%24select%5b7%5d=parentReference&%24select%5b8%5d=webUrl', + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAllLimitSimplify.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAllLimitSimplify.workflow.json new file mode 100644 index 0000000000..494eef2579 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/getAllLimitSimplify.workflow.json @@ -0,0 +1,82 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "limit": 2, + "options": {}, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "createdDateTime": "2025-03-12T22:18:18Z", + "id": "item1", + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000", + "fields": { + "Title": "Item 1" + } + } + }, + { + "json": { + "createdDateTime": "2025-03-12T22:18:18Z", + "id": "item2", + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/1_.000", + "fields": { + "Title": "Item 2" + } + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/update.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/update.test.ts new file mode 100644 index 0000000000..65a0038a98 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/update.test.ts @@ -0,0 +1,87 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['update.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'patch', + path: '/sites/site1/lists/list1/items/item1', + statusCode: 200, + requestBody: { + fields: { + Title: 'Title 2', + }, + }, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-25T12:26:12Z', + eTag: '"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2"', + id: 'item1', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-25T12:26:46Z', + parentReference: { + id: '84070a73-ea24-463c-8eb2-0e9afa11c63f', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/3_.000', + contentType: { + id: '0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8', + name: 'Item', + }, + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2"', + Title: 'Title 2', + ID: 'item1', + ContentType: 'Item', + Modified: '2025-03-25T12:26:46Z', + Created: '2025-03-25T12:26:12Z', + AuthorLookupId: '7', + EditorLookupId: '7', + _UIVersionString: '2.0', + Attachments: false, + Edit: '', + LinkTitleNoMenu: 'Title 2', + LinkTitle: 'Title 2', + ItemChildCount: '0', + FolderChildCount: '0', + _ComplianceFlags: '', + _ComplianceTag: '', + _ComplianceTagWrittenTime: '', + _ComplianceTagUserId: '', + AppAuthorLookupId: '5', + AppEditorLookupId: '5', + }, + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/update.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/update.workflow.json new file mode 100644 index 0000000000..b01df69dac --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/update.workflow.json @@ -0,0 +1,154 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "operation": "update", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "Title": "Title 2", + "id": "item1" + }, + "matchingColumns": ["id"], + "schema": [ + { + "id": "Title", + "canBeUsedToMatch": false, + "defaultMatch": false, + "display": true, + "displayName": "Title", + "readOnly": false, + "required": false, + "type": "string" + }, + { + "id": "id", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, + "displayName": "ID", + "readOnly": true, + "required": true, + "type": "string", + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity", + "@odata.etag": "\"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2\"", + "createdBy": { + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-25T12:26:12Z", + "eTag": "\"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2\"", + "id": "item1", + "lastModifiedBy": { + "application": { + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa", + "displayName": "sharepoint-n8n-test" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-25T12:26:46Z", + "parentReference": { + "id": "84070a73-ea24-463c-8eb2-0e9afa11c63f", + "listId": "list1", + "siteId": "site1" + }, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/3_.000", + "contentType": { + "id": "0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8", + "name": "Item" + }, + "fields@odata.navigationLink": "sites/site1/lists/list1/items/item1/fields", + "fields": { + "@odata.etag": "\"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2\"", + "Title": "Title 2", + "ID": "item1", + "ContentType": "Item", + "Modified": "2025-03-25T12:26:46Z", + "Created": "2025-03-25T12:26:12Z", + "AuthorLookupId": "7", + "EditorLookupId": "7", + "_UIVersionString": "2.0", + "Attachments": false, + "Edit": "", + "LinkTitleNoMenu": "Title 2", + "LinkTitle": "Title 2", + "ItemChildCount": "0", + "FolderChildCount": "0", + "_ComplianceFlags": "", + "_ComplianceTag": "", + "_ComplianceTagWrittenTime": "", + "_ComplianceTagUserId": "", + "AppAuthorLookupId": "5", + "AppEditorLookupId": "5" + } + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/upsert.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/upsert.test.ts new file mode 100644 index 0000000000..d7293e82ba --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/upsert.test.ts @@ -0,0 +1,136 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['upsert.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists/list1/items?%24filter=fields%2FTitle%20eq%20%27Title%201%27', + statusCode: 200, + requestHeaders: { + Prefer: 'HonorNonIndexedQueriesWarningMayFailRandomly', + }, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems', + value: [ + { + '@odata.etag': '"0ea4148a-f8e5-4f2f-a815-2e2be693b164,2"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-25T12:48:45Z', + eTag: '"0ea4148a-f8e5-4f2f-a815-2e2be693b164,2"', + id: 'item1', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-25T13:01:45Z', + parentReference: { + id: '0ead9135-9622-4b6d-b319-6b8810a6dc60', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/test/19_.000', + contentType: { + id: '0x0100362657F7588C5C438072A77E0EF184F4000272C0D1046D984B8097B3C00D199EDE', + name: 'Item', + }, + }, + ], + }, + }, + { + method: 'patch', + path: '/sites/site1/lists/list1/items/item1', + statusCode: 200, + requestBody: { + fields: { + Title: 'Title 1', + }, + }, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity', + '@odata.etag': '"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2"', + createdBy: { + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + createdDateTime: '2025-03-25T12:26:12Z', + eTag: '"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2"', + id: 'item1', + lastModifiedBy: { + application: { + id: 'b9c26603-3c9b-4050-b848-27dfab0a52fa', + displayName: 'sharepoint-n8n-test', + }, + user: { + displayName: 'John Doe', + email: 'john@doe.onmicrosoft.com', + id: '5f7afebb-121d-4664-882b-a09fe6584ce0', + }, + }, + lastModifiedDateTime: '2025-03-25T12:26:46Z', + parentReference: { + id: '84070a73-ea24-463c-8eb2-0e9afa11c63f', + listId: 'list1', + siteId: 'site1', + }, + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/3_.000', + contentType: { + id: '0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8', + name: 'Item', + }, + 'fields@odata.navigationLink': 'sites/site1/lists/list1/items/item1/fields', + fields: { + '@odata.etag': '"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2"', + Title: 'Title 1', + ID: 'item1', + ContentType: 'Item', + Modified: '2025-03-25T12:26:46Z', + Created: '2025-03-25T12:26:12Z', + AuthorLookupId: '7', + EditorLookupId: '7', + _UIVersionString: '2.0', + Attachments: false, + Edit: '', + LinkTitleNoMenu: 'Title 1', + LinkTitle: 'Title 1', + ItemChildCount: '0', + FolderChildCount: '0', + _ComplianceFlags: '', + _ComplianceTag: '', + _ComplianceTagWrittenTime: '', + _ComplianceTagUserId: '', + AppAuthorLookupId: '5', + AppEditorLookupId: '5', + }, + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/upsert.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/upsert.workflow.json new file mode 100644 index 0000000000..447354693f --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/item/upsert.workflow.json @@ -0,0 +1,143 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "item", + "operation": "upsert", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "columns": { + "mappingMode": "defineBelow", + "value": { + "Title": "Title 1" + }, + "matchingColumns": ["Title"], + "schema": [ + { + "id": "Title", + "canBeUsedToMatch": true, + "defaultMatch": false, + "display": true, + "displayName": "Title", + "readOnly": false, + "required": true, + "type": "string", + "removed": false + } + ], + "attemptToConvertTypes": false, + "convertFieldsToString": false + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "@odata.context": "https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#listItems/$entity", + "@odata.etag": "\"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2\"", + "createdBy": { + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "createdDateTime": "2025-03-25T12:26:12Z", + "eTag": "\"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2\"", + "id": "item1", + "lastModifiedBy": { + "application": { + "id": "b9c26603-3c9b-4050-b848-27dfab0a52fa", + "displayName": "sharepoint-n8n-test" + }, + "user": { + "displayName": "John Doe", + "email": "john@doe.onmicrosoft.com", + "id": "5f7afebb-121d-4664-882b-a09fe6584ce0" + } + }, + "lastModifiedDateTime": "2025-03-25T12:26:46Z", + "parentReference": { + "id": "84070a73-ea24-463c-8eb2-0e9afa11c63f", + "listId": "list1", + "siteId": "site1" + }, + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list/3_.000", + "contentType": { + "id": "0x010010D603DC4CF2DF4BB8A2D75DCB4BB1B30037A4993FB4DEB0439C3DC6DEC95A9DF8", + "name": "Item" + }, + "fields@odata.navigationLink": "sites/site1/lists/list1/items/item1/fields", + "fields": { + "@odata.etag": "\"cc40561f-3d3b-4cfb-b8a9-8af9d71de3f0,2\"", + "Title": "Title 1", + "ID": "item1", + "ContentType": "Item", + "Modified": "2025-03-25T12:26:46Z", + "Created": "2025-03-25T12:26:12Z", + "AuthorLookupId": "7", + "EditorLookupId": "7", + "_UIVersionString": "2.0", + "Attachments": false, + "Edit": "", + "LinkTitleNoMenu": "Title 1", + "LinkTitle": "Title 1", + "ItemChildCount": "0", + "FolderChildCount": "0", + "_ComplianceFlags": "", + "_ComplianceTag": "", + "_ComplianceTagWrittenTime": "", + "_ComplianceTagUserId": "", + "AppAuthorLookupId": "5", + "AppEditorLookupId": "5" + } + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/get.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/get.test.ts new file mode 100644 index 0000000000..5691b1dcdb --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/get.test.ts @@ -0,0 +1,33 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['get.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists/list1?%24select=id%2Cname%2CdisplayName%2Cdescription%2CcreatedDateTime%2ClastModifiedDateTime%2CwebUrl', + statusCode: 200, + responseBody: { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#lists/$entity', + '@odata.etag': '"58a279af-1f06-4392-a5ed-2b37fa1d6c1d,5"', + createdDateTime: '2025-03-12T19:38:40Z', + description: 'My List 1', + id: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + lastModifiedDateTime: '2025-03-12T22:18:18Z', + name: 'list1', + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list', + displayName: 'list1', + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/get.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/get.workflow.json new file mode 100644 index 0000000000..51c49d40cd --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/get.workflow.json @@ -0,0 +1,70 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "list", + "operation": "get", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "list": { + "__rl": true, + "value": "list1", + "mode": "list", + "cachedResultName": "list1" + }, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "createdDateTime": "2025-03-12T19:38:40Z", + "description": "My List 1", + "id": "58a279af-1f06-4392-a5ed-2b37fa1d6c1d", + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "name": "list1", + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list", + "displayName": "list1" + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/getAll.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/getAll.test.ts new file mode 100644 index 0000000000..94b41bb734 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/getAll.test.ts @@ -0,0 +1,60 @@ +import { NodeTestHarness } from '@nodes-testing/node-test-harness'; + +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + const { baseUrl } = credentials.microsoftSharePointOAuth2Api; + new NodeTestHarness().setupTests({ + credentials, + workflowFiles: ['getAll.workflow.json'], + nock: { + baseUrl, + mocks: [ + { + method: 'get', + path: '/sites/site1/lists?%24select=id%2Cname%2CdisplayName%2Cdescription%2CcreatedDateTime%2ClastModifiedDateTime%2CwebUrl', + statusCode: 200, + responseBody: { + value: [ + { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#lists/$entity', + '@odata.etag': '"58a279af-1f06-4392-a5ed-2b37fa1d6c1d,5"', + createdDateTime: '2025-03-12T19:38:40Z', + description: 'My List 1', + id: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + lastModifiedDateTime: '2025-03-12T22:18:18Z', + name: 'list1', + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list', + displayName: 'list1', + }, + ], + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB&%24top=1&%24select=id%2cname%2cdisplayName%2cdescription%2ccreatedDateTime%2clastModifiedDateTime%2cwebUrl', + }, + }, + { + method: 'get', + path: '/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB&%24top=1&%24select=id%2cname%2cdisplayName%2cdescription%2ccreatedDateTime%2clastModifiedDateTime%2cwebUrl', + statusCode: 200, + responseBody: { + value: [ + { + '@odata.context': + 'https://mydomain.sharepoint.com/sites/site1/_api/v2.0/$metadata#lists/$entity', + '@odata.etag': '"58a279af-1f06-4392-a5ed-2b37fa1d6c1d,5"', + createdDateTime: '2025-03-12T19:38:40Z', + description: 'My List 2', + id: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + lastModifiedDateTime: '2025-03-12T22:18:18Z', + name: 'list2', + webUrl: 'https://mydomain.sharepoint.com/sites/site1/Lists/name%20list', + displayName: 'list2', + }, + ], + }, + }, + ], + }, + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/getAll.workflow.json b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/getAll.workflow.json new file mode 100644 index 0000000000..c7a62afd66 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/list/getAll.workflow.json @@ -0,0 +1,76 @@ +{ + "nodes": [ + { + "parameters": {}, + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [0, 0], + "id": "d8d29d0a-bb31-4094-a252-8008932f5425", + "name": "When clicking ‘Test workflow’" + }, + { + "parameters": { + "resource": "list", + "operation": "getAll", + "site": { + "__rl": true, + "value": "site1", + "mode": "list", + "cachedResultName": "site1" + }, + "returnAll": true, + "requestOptions": {} + }, + "type": "n8n-nodes-base.microsoftSharePoint", + "typeVersion": 1, + "position": [200, 0], + "id": "0e19be10-9d94-4654-89e9-432daa8102cb", + "name": "Microsoft SharePoint", + "credentials": { + "microsoftSharePointOAuth2Api": { + "id": "cXXnMCWyk397M5qJ", + "name": "Microsoft SharePoint account" + } + } + } + ], + "connections": { + "When clicking ‘Test workflow’": { + "main": [ + [ + { + "node": "Microsoft SharePoint", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": { + "Microsoft SharePoint": [ + { + "json": { + "createdDateTime": "2025-03-12T19:38:40Z", + "description": "My List 1", + "id": "58a279af-1f06-4392-a5ed-2b37fa1d6c1d", + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "name": "list1", + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list", + "displayName": "list1" + } + }, + { + "json": { + "createdDateTime": "2025-03-12T19:38:40Z", + "description": "My List 2", + "id": "58a279af-1f06-4392-a5ed-2b37fa1d6c1d", + "lastModifiedDateTime": "2025-03-12T22:18:18Z", + "name": "list2", + "webUrl": "https://mydomain.sharepoint.com/sites/site1/Lists/name%20list", + "displayName": "list2" + } + } + ] + } +} diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/methods/listSearch.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/methods/listSearch.test.ts new file mode 100644 index 0000000000..ce43fd7bb0 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/methods/listSearch.test.ts @@ -0,0 +1,476 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { MicrosoftSharePoint } from '../../MicrosoftSharePoint.node'; +import { credentials } from '../credentials'; + +describe('Microsoft SharePoint Node', () => { + describe('List search', () => { + let loadOptionsFunctions: MockProxy; + let mockRequestWithAuthentication: jest.Mock; + let node: MicrosoftSharePoint; + + beforeEach(() => { + loadOptionsFunctions = mock(); + mockRequestWithAuthentication = jest.fn(); + loadOptionsFunctions.helpers.httpRequestWithAuthentication = mockRequestWithAuthentication; + loadOptionsFunctions.getCredentials.mockResolvedValue( + credentials.microsoftSharePointOAuth2Api, + ); + node = new MicrosoftSharePoint(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should list search files', async () => { + const mockResponse = { + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + value: [ + { + '@odata.etag': '"{70EC0A2F-6C3E-425F-BBE2-1D4E758F90EE},1"', + id: '01SPEVVYBPBLWHAPTML5BLXYQ5JZ2Y7EHO', + name: 'Folder1', + }, + { + '@odata.etag': '"{10F06786-5AAF-434A-8A4E-3ED44DE4A987},6"', + id: '01SPEVVYEGM7YBBL22JJBYUTR62RG6JKMH', + name: 'file2.jpg', + file: { + hashes: { + quickXorHash: 'uFgaWYgAVo55sg4DIR9BTMzlmH8=', + }, + irmEffectivelyEnabled: false, + irmEnabled: false, + mimeType: 'image/jpeg', + }, + }, + { + '@odata.etag': '"{0EC452E0-D0F7-4ECF-87D7-B738F865313B},1"', + id: '01SPEVVYHAKLCA556QZ5HIPV5XHD4GKMJ3', + name: 'file1.txt', + file: { + hashes: { + quickXorHash: 'AAAAAAAAAAAAAAAAAAAAAAAAAAA=', + }, + irmEffectivelyEnabled: false, + irmEnabled: false, + mimeType: 'text/plain', + }, + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('folder'); + + const listSearchResult = await node.methods.listSearch.getFiles.call( + loadOptionsFunctions, + 'file', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + qs: { + $filter: "name eq 'file'", + }, + }); + expect(listSearchResult).toEqual({ + results: [ + { name: 'file1.txt', value: '01SPEVVYHAKLCA556QZ5HIPV5XHD4GKMJ3' }, + { name: 'file2.jpg', value: '01SPEVVYEGM7YBBL22JJBYUTR62RG6JKMH' }, + ], + paginationToken: + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + }); + + it('should list search files with pagination', async () => { + const mockResponse = { + value: [ + { + '@odata.etag': '"{0EC452E0-D0F7-4ECF-87D7-B738F865313B},1"', + id: '01SPEVVYHAKLCA556QZ5HIPV5XHD4GKMJ3', + name: 'file1.txt', + file: { + hashes: { + quickXorHash: 'AAAAAAAAAAAAAAAAAAAAAAAAAAA=', + }, + irmEffectivelyEnabled: false, + irmEnabled: false, + mimeType: 'text/plain', + }, + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('folder'); + + const listSearchResult = await node.methods.listSearch.getFiles.call( + loadOptionsFunctions, + undefined, + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + url: 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + expect(listSearchResult).toEqual({ + results: [{ name: 'file1.txt', value: '01SPEVVYHAKLCA556QZ5HIPV5XHD4GKMJ3' }], + }); + }); + + it('should list search folders', async () => { + const mockResponse = { + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + value: [ + { + '@odata.etag': '"{529A466D-D708-4857-B771-24F467546A2A},1"', + id: '01SPEVVYDNI2NFECGXK5ELO4JE6RTVI2RK', + name: 'Folder2', + folder: { + childCount: 1, + view: {}, + }, + }, + { + '@odata.etag': '"{A3B2AE2A-2099-4194-A38B-EC7182CA3000},1"', + id: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + name: 'Folder1', + folder: { + childCount: 7, + view: {}, + }, + }, + { + '@odata.etag': '"{A6EF0C5E-0248-482A-AD79-0EFDB6622473},1"', + id: '01SPEVVYC6BTX2MSACFJEK26IO7W3GEJDT', + name: 'file.txt', + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + + const listSearchResult = await node.methods.listSearch.getFolders.call( + loadOptionsFunctions, + 'folder', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + qs: { + $filter: "name eq 'folder'", + }, + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'Folder1', + value: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + }, + { + name: 'Folder2', + value: '01SPEVVYDNI2NFECGXK5ELO4JE6RTVI2RK', + }, + ], + paginationToken: + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + }); + + it('should list search folders with pagination', async () => { + const mockResponse = { + value: [ + { + '@odata.etag': '"{A3B2AE2A-2099-4194-A38B-EC7182CA3000},1"', + id: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + name: 'Folder1', + folder: { + childCount: 7, + view: {}, + }, + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + + const listSearchResult = await node.methods.listSearch.getFolders.call( + loadOptionsFunctions, + undefined, + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + url: 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/items?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'Folder1', + value: '01SPEVVYBKV2ZKHGJASRA2HC7MOGBMUMAA', + }, + ], + }); + }); + + it('should list search items', async () => { + const mockResponse = { + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/listItems?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + value: [ + { + '@odata.etag': '"98af0386-66bd-4524-a653-80be05e0f14d,2"', + id: '2', + 'fields@odata.navigationLink': 'fields', + fields: { + '@odata.etag': '"98af0386-66bd-4524-a653-80be05e0f14d,2"', + Title: 'Title 2', + }, + }, + { + '@odata.etag': '"0ea4148a-f8e5-4f2f-a815-2e2be693b164,4"', + id: '1', + 'fields@odata.navigationLink': 'fields', + fields: { + '@odata.etag': '"0ea4148a-f8e5-4f2f-a815-2e2be693b164,4"', + Title: 'Title 1', + }, + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('list'); + + const listSearchResult = await node.methods.listSearch.getItems.call( + loadOptionsFunctions, + 'Title', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + qs: { + $filter: "fields/Title eq 'Title'", + }, + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'Title 1', + value: '1', + }, + { + name: 'Title 2', + value: '2', + }, + ], + paginationToken: + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/listItems?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + }); + + it('should list search items with pagination', async () => { + const mockResponse = { + value: [ + { + '@odata.etag': '"0ea4148a-f8e5-4f2f-a815-2e2be693b164,4"', + id: '1', + 'fields@odata.navigationLink': 'fields', + fields: { + '@odata.etag': '"0ea4148a-f8e5-4f2f-a815-2e2be693b164,4"', + }, + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('list'); + + const listSearchResult = await node.methods.listSearch.getItems.call( + loadOptionsFunctions, + undefined, + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/listItems?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + url: 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/listItems?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: '1', + value: '1', + }, + ], + }); + }); + + it('should list search lists', async () => { + const mockResponse = { + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + value: [ + { + '@odata.etag': '"58a279af-1f06-4392-a5ed-2b37fa1d6c1d,5"', + id: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + displayName: 'List 2', + }, + { + '@odata.etag': '"23af565a-da89-48f0-ae5f-2d4a7244b446,0"', + id: '23af565a-da89-48f0-ae5f-2d4a7244b446', + displayName: 'List 1', + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + + const listSearchResult = await node.methods.listSearch.getLists.call( + loadOptionsFunctions, + 'List', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + qs: { + $filter: "displayName eq 'List'", + }, + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'List 1', + value: '23af565a-da89-48f0-ae5f-2d4a7244b446', + }, + { + name: 'List 2', + value: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + }, + ], + paginationToken: + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + }); + + it('should list search lists with pagination', async () => { + const mockResponse = { + value: [ + { + '@odata.etag': '"23af565a-da89-48f0-ae5f-2d4a7244b446,0"', + id: '23af565a-da89-48f0-ae5f-2d4a7244b446', + displayName: 'List 1', + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + + const listSearchResult = await node.methods.listSearch.getLists.call( + loadOptionsFunctions, + undefined, + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + url: 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/lists?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'List 1', + value: '23af565a-da89-48f0-ae5f-2d4a7244b446', + }, + ], + }); + }); + + it('should list search sites', async () => { + const mockResponse = { + '@odata.nextLink': + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/sites?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + value: [ + { + id: 'mydomain.sharepoint.com,cf38a104-c767-48c7-93d0-c093e3909c78,3abe66fd-ec23-469f-abc6-332e3a95bf75', + title: 'Site 2', + }, + { + id: 'mydomain.sharepoint.com,3286a459-bc45-4aab-8200-b9ba7edcca96,3abe66fd-ec23-469f-abc6-332e3a95bf75', + title: 'Site 1', + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + + const listSearchResult = await node.methods.listSearch.getSites.call( + loadOptionsFunctions, + 'Site', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + qs: { + $search: 'Site', + }, + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'Site 1', + value: + 'mydomain.sharepoint.com,3286a459-bc45-4aab-8200-b9ba7edcca96,3abe66fd-ec23-469f-abc6-332e3a95bf75', + }, + { + name: 'Site 2', + value: + 'mydomain.sharepoint.com,cf38a104-c767-48c7-93d0-c093e3909c78,3abe66fd-ec23-469f-abc6-332e3a95bf75', + }, + ], + paginationToken: + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/sites?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + }); + + it('should list search sites and pagination', async () => { + const mockResponse = { + value: [ + { + id: 'mydomain.sharepoint.com,3286a459-bc45-4aab-8200-b9ba7edcca96,3abe66fd-ec23-469f-abc6-332e3a95bf75', + title: 'Site 1', + }, + ], + }; + mockRequestWithAuthentication.mockReturnValue(mockResponse); + + const listSearchResult = await node.methods.listSearch.getSites.call( + loadOptionsFunctions, + undefined, + 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/sites?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + ); + + expect(mockRequestWithAuthentication).toHaveBeenCalledTimes(1); + expect(mockRequestWithAuthentication.mock.calls[0][1]).toMatchObject({ + url: 'https://mydomain.sharepoint.com/_api/v2.0/sites(%27mydomain.sharepoint.com,site1%27)/sites?%24skiptoken=aWQ9MjFFQkEzOUMtMkU3My00NzgwLUFBQzEtMTVDNzlDMTk4QjlB', + }); + expect(listSearchResult).toEqual({ + results: [ + { + name: 'Site 1', + value: + 'mydomain.sharepoint.com,3286a459-bc45-4aab-8200-b9ba7edcca96,3abe66fd-ec23-469f-abc6-332e3a95bf75', + }, + ], + }); + }); + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/test/methods/resourceMapping.test.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/test/methods/resourceMapping.test.ts new file mode 100644 index 0000000000..a67d7b4475 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/test/methods/resourceMapping.test.ts @@ -0,0 +1,886 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { ILoadOptionsFunctions } from 'n8n-workflow'; + +import { getMappingColumns } from '../../methods/resourceMapping'; + +describe('Microsoft SharePoint Node', () => { + describe('Resource mapping', () => { + let loadOptionsFunctions: MockProxy; + + beforeEach(() => { + loadOptionsFunctions = mock(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should map columns', async () => { + loadOptionsFunctions.getCredentials.mockResolvedValue({}); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('list'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('create'); + const mockResponse = { + value: [ + { + '@odata.etag': '"0"', + id: '0x0100362657F7588C5C438072A77E0EF184F4000272C0D1046D984B8097B3C00D199EDE', + description: 'Create a new list item.', + eTag: '"0"', + group: 'List Content Types', + hidden: false, + name: 'Item', + parentId: '0x01', + readOnly: false, + sealed: false, + columns: [ + { + displayName: 'Title', + '@odata.etag': '"1"', + columnGroup: 'Custom Columns', + customFormatter: '', + description: 'Description Title', + enforceUniqueValues: true, + eTag: '"1"', + hidden: false, + id: 'fa564e0f-0c70-4ab9-b863-0177e6ddd247', + indexed: true, + isDeletable: false, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'Title', + readOnly: false, + required: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: true, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'Othertitle', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Descriptio Othertitle', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '82642ec8-ef9b-478f-acf9-31f7d45fbc31', + indexed: false, + isDeletable: false, + isReorderable: false, + isSealed: false, + propagateChanges: false, + name: 'LinkTitle', + readOnly: true, + type: 'unknownFutureValue', + default: '', + }, + { + displayName: 'Choice', + '@odata.etag': '"2"', + columnGroup: 'Custom Columns', + customFormatter: + '{"elmType":"div","style":{"flex-wrap":"wrap","display":"flex"},"children":[{"elmType":"div","style":{"box-sizing":"border-box","padding":"4px 8px 5px 8px","overflow":"hidden","text-overflow":"ellipsis","display":"flex","border-radius":"16px","height":"24px","align-items":"center","white-space":"nowrap","margin":"4px 4px 4px 4px"},"attributes":{"class":{"operator":":","operands":[{"operator":"==","operands":["[$choice]","Choice 1"]},"sp-css-backgroundColor-BgCornflowerBlue sp-css-color-CornflowerBlueFont",{"operator":":","operands":[{"operator":"==","operands":["[$choice]","Choice 2"]},"sp-css-backgroundColor-BgMintGreen sp-css-color-MintGreenFont",{"operator":":","operands":[{"operator":"==","operands":["[$choice]","Choice 3"]},"sp-css-backgroundColor-BgGold sp-css-color-GoldFont",{"operator":":","operands":[{"operator":"==","operands":["[$choice]",""]},"","sp-field-borderAllRegular sp-field-borderAllSolid sp-css-borderColor-neutralSecondary"]}]}]}]}},"children":[{"elmType":"span","style":{"overflow":"hidden","text-overflow":"ellipsis","padding":"0 3px"},"txtContent":"[$choice]","attributes":{"class":{"operator":":","operands":[{"operator":"==","operands":["[$choice]","Choice 1"]},"sp-css-color-CornflowerBlueFont",{"operator":":","operands":[{"operator":"==","operands":["[$choice]","Choice 2"]},"sp-css-color-MintGreenFont",{"operator":":","operands":[{"operator":"==","operands":["[$choice]","Choice 3"]},"sp-css-color-GoldFont",{"operator":":","operands":[{"operator":"==","operands":["[$choice]",""]},"",""]}]}]}]}}}]}],"templateId":"BgColorChoicePill"}', + description: 'Description Choice', + enforceUniqueValues: false, + eTag: '"2"', + hidden: false, + id: '21d4577a-e5f2-461d-a9cf-f7956c502f4e', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'choice', + readOnly: false, + type: 'choice', + default: '', + choice: { + allowTextEntry: false, + choices: ['Choice 1', 'Choice 2', 'Choice 3'], + displayAs: 'dropDownMenu', + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'Datetime', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Datetime', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '7e2cf00a-1df6-4c1c-8ea9-d92ef3c7760e', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'datetime', + readOnly: false, + type: 'dateTime', + default: '', + dateTime: { + displayAs: 'standard', + format: 'dateOnly', + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'Person', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Person', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '574f4483-ec83-41a0-b772-67b331695c56', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'person', + readOnly: false, + type: 'user', + default: '', + personOrGroup: { + allowMultipleSelection: false, + displayAs: 'nameWithPresence', + format: 'peopleOnly', + hasPrefix: false, + noLookupFieldHeader: false, + }, + }, + { + displayName: 'Number', + '@odata.etag': '"2"', + columnGroup: 'Custom Columns', + description: 'Description Number', + enforceUniqueValues: false, + eTag: '"2"', + hidden: false, + id: 'c69c2fc9-b6fd-4a08-8750-a293b7c888a3', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'number', + readOnly: false, + type: 'number', + default: 0, + number: { + decimalPlaces: 'automatic', + displayAs: 'number', + maximum: 1.7976931348623157e308, + minimum: -1.7976931348623157e308, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'Bool', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Whether Description Bool', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '955880c3-8c95-4609-93ef-cdbd2deef1b2', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'bool', + readOnly: false, + type: 'boolean', + default: false, + boolean: {}, + defaultValue: { + value: '1', + }, + }, + { + displayName: 'Hyperlink', + '@odata.etag': '"2"', + columnGroup: 'Custom Columns', + description: 'Description Hyperlink', + enforceUniqueValues: false, + eTag: '"2"', + hidden: false, + id: 'a6352b41-95e7-4cbf-9184-aae4fe92d865', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'hyperlink', + readOnly: false, + type: 'url', + default: '', + hyperlinkOrPicture: { + isPicture: false, + }, + }, + { + displayName: 'Currency', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Currency', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '40d9b8f9-05e0-4105-bc35-0c969eeb0096', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'currency', + readOnly: false, + type: 'currency', + default: '', + currency: { + decimalPlaces: 'automatic', + displayAs: 'number', + locale: 'en-US', + maximum: 1.7976931348623157e308, + minimum: -1.7976931348623157e308, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'Location', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '5bfb0a2c-04ed-4a59-ae6e-de0d97c8352b', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'location', + readOnly: false, + type: 'location', + default: '', + location: {}, + }, + { + displayName: 'location: Country/Region', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: Country/Region', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: 'db61a63e-558b-4e41-b895-2da2f40a7a23', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'CountryOrRegion', + readOnly: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: false, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'location: State', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: State', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: 'c24ae779-ee80-4e8c-b291-c716f1fdce66', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'State', + readOnly: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: false, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'location: City', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: City', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '7fb3c8e7-8a8c-4488-a768-4db6922b53f4', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'City', + readOnly: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: false, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'location: Postal Code', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: Postal Code', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '1fa93efa-579f-48cb-b1e3-f2a8fdb615fe', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'PostalCode', + readOnly: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: false, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'location: Street', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: Street', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '1ce8048e-a619-40fb-86b2-c5963c0a7526', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'Street', + readOnly: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: false, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'location: Coordinates', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: Coordinates', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '079b0670-2a3d-4a71-abe5-c0023a66879d', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'GeoLoc', + readOnly: true, + type: 'geolocation', + default: '', + geolocation: {}, + }, + { + displayName: 'location: Name', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: Name', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '73a8bcce-484b-4fd6-84be-47ef648c9c45', + indexed: false, + isDeletable: true, + isReorderable: false, + isSealed: true, + propagateChanges: false, + name: 'DispName', + readOnly: true, + type: 'text', + default: '', + text: { + allowMultipleLines: false, + appendChangesToExistingText: false, + autoHyperLink: false, + linesForEditing: 0, + maxLength: 255, + }, + validation: { + defaultLanguage: 'en-US', + descriptions: [ + { + languageTag: 'en-US', + }, + ], + }, + }, + { + displayName: 'Image', + '@odata.etag': '"0"', + columnGroup: 'Custom Columns', + description: 'Description Location: Image', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: '2a485bec-bab9-493b-9214-2be0b8566a25', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'image', + readOnly: false, + type: 'thumbnail', + default: '', + thumbnail: {}, + }, + { + displayName: 'Metadata', + '@odata.etag': '"2"', + columnGroup: 'Custom Columns', + customFormatter: '', + description: 'Description Location: Metadata', + enforceUniqueValues: false, + eTag: '"2"', + hidden: false, + id: '2d8cb8ec-4a8f-43ae-87fa-e832b4631f0d', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'metadata', + readOnly: false, + staticName: 'metadata', + type: 'term', + default: '', + defaultValue: { + value: '', + }, + term: { + allowMultipleValues: false, + showFullyQualifiedName: false, + }, + }, + { + displayName: 'Lookup', + '@odata.etag': '"2"', + columnGroup: 'Custom Columns', + description: 'Description Lookup', + enforceUniqueValues: false, + eTag: '"2"', + hidden: false, + id: '44bdab3a-ac89-4b40-acfd-707510f93719', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'lookup', + readOnly: false, + type: 'lookup', + default: '', + lookup: { + allowMultipleValues: false, + allowUnlimitedLength: false, + columnName: 'Title', + displayFormUrl: + 'https://mydomain.sharepoint.com/sites/n8ntest10/_layouts/15/listform.aspx?PageType=4&ListId=58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + listId: '58a279af-1f06-4392-a5ed-2b37fa1d6c1d', + }, + }, + { + displayName: 'Rating (0-5)', + '@odata.etag': '"1"', + columnGroup: 'Content Feedback', + description: 'Average value of all the ratings that have been submitted', + enforceUniqueValues: false, + eTag: '"1"', + hidden: false, + id: '5a14d1ab-1513-48c7-97b3-657a5ba6c742', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'AverageRating', + readOnly: false, + type: 'unknownFutureValue', + default: '', + }, + { + displayName: 'Number of Ratings', + '@odata.etag': '"0"', + columnGroup: 'Content Feedback', + description: 'Number of ratings submitted', + enforceUniqueValues: false, + eTag: '"0"', + hidden: false, + id: 'b1996002-9167-45e5-a4df-b2c41c6723c7', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'RatingCount', + readOnly: false, + type: 'unknownFutureValue', + default: '', + }, + { + displayName: 'Number of Likes', + '@odata.etag': '"1"', + columnGroup: 'Content Feedback', + description: 'Description Number of Likes', + enforceUniqueValues: false, + eTag: '"1"', + hidden: false, + id: '6e4d832b-f610-41a8-b3e0-239608efda41', + indexed: false, + isDeletable: true, + isReorderable: true, + isSealed: false, + propagateChanges: false, + name: 'LikesCount', + readOnly: false, + type: 'unknownFutureValue', + default: '', + }, + ], + }, + ], + }; + loadOptionsFunctions.helpers.httpRequestWithAuthentication = jest + .fn() + .mockReturnValue(mockResponse); + + const fields = await getMappingColumns.call(loadOptionsFunctions); + + expect(fields).toEqual({ + fields: [ + { + id: 'Title', + canBeUsedToMatch: true, + defaultMatch: false, + display: true, + displayName: 'Title', + readOnly: false, + required: true, + type: 'string', + }, + { + id: 'choice', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Choice', + readOnly: false, + required: undefined, + type: 'options', + options: [ + { + name: 'Choice 1', + value: 'Choice 1', + }, + { + name: 'Choice 2', + value: 'Choice 2', + }, + { + name: 'Choice 3', + value: 'Choice 3', + }, + ], + }, + { + id: 'datetime', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Datetime', + readOnly: false, + required: undefined, + type: 'dateTime', + }, + { + id: 'person', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Person', + readOnly: false, + required: undefined, + type: 'string', + }, + { + id: 'number', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Number', + readOnly: false, + required: undefined, + type: 'number', + }, + { + id: 'bool', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Bool', + readOnly: false, + required: undefined, + type: 'boolean', + }, + { + id: 'hyperlink', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Hyperlink', + readOnly: true, + required: undefined, + type: undefined, + }, + { + id: 'currency', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Currency', + readOnly: false, + required: undefined, + type: 'number', + }, + { + id: 'location', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Location', + readOnly: true, + required: undefined, + type: undefined, + }, + { + id: 'image', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Image', + readOnly: false, + required: undefined, + type: 'object', + }, + { + id: 'metadata', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Metadata', + readOnly: true, + required: undefined, + type: undefined, + }, + { + id: 'lookup', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Lookup', + readOnly: false, + required: undefined, + type: 'string', + }, + { + id: 'AverageRating', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Rating (0-5)', + readOnly: false, + required: undefined, + type: 'number', + }, + { + id: 'RatingCount', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Number of Ratings', + readOnly: false, + required: undefined, + type: 'number', + }, + { + id: 'LikesCount', + canBeUsedToMatch: false, + defaultMatch: false, + display: true, + displayName: 'Number of Likes', + readOnly: false, + required: undefined, + type: 'number', + }, + ], + }); + }); + + it('should map columns with id for update', async () => { + loadOptionsFunctions.getCredentials.mockResolvedValue({}); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('site'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('list'); + loadOptionsFunctions.getNodeParameter.mockReturnValueOnce('update'); + const mockResponse = { + value: [ + { + columns: [], + }, + ], + }; + loadOptionsFunctions.helpers.httpRequestWithAuthentication = jest + .fn() + .mockReturnValue(mockResponse); + + const fields = await getMappingColumns.call(loadOptionsFunctions); + + expect(fields).toEqual({ + fields: [ + { + id: 'id', + canBeUsedToMatch: true, + defaultMatch: false, + display: true, + displayName: 'ID', + readOnly: true, + required: true, + type: 'string', + }, + ], + }); + }); + }); +}); diff --git a/packages/nodes-base/nodes/Microsoft/SharePoint/transport/index.ts b/packages/nodes-base/nodes/Microsoft/SharePoint/transport/index.ts new file mode 100644 index 0000000000..67bd68d576 --- /dev/null +++ b/packages/nodes-base/nodes/Microsoft/SharePoint/transport/index.ts @@ -0,0 +1,37 @@ +import type { + IDataObject, + IExecuteFunctions, + IExecuteSingleFunctions, + IHttpRequestMethods, + IHttpRequestOptions, + ILoadOptionsFunctions, +} from 'n8n-workflow'; + +export async function microsoftSharePointApiRequest( + this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, + method: IHttpRequestMethods, + endpoint: string, + body: IDataObject | Buffer = {}, + qs?: IDataObject, + headers?: IDataObject, + url?: string, +): Promise { + const credentials: { subdomain: string } = await this.getCredentials( + 'microsoftSharePointOAuth2Api', + ); + + const options: IHttpRequestOptions = { + method, + url: url ?? `https://${credentials.subdomain}.sharepoint.com/_api/v2.0${endpoint}`, + json: true, + headers, + body, + qs, + }; + + return await this.helpers.httpRequestWithAuthentication.call( + this, + 'microsoftSharePointOAuth2Api', + options, + ); +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3fe369c434..65b8c4cb94 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -652,6 +652,7 @@ "dist/nodes/Microsoft/OneDrive/MicrosoftOneDriveTrigger.node.js", "dist/nodes/Microsoft/Outlook/MicrosoftOutlook.node.js", "dist/nodes/Microsoft/Outlook/MicrosoftOutlookTrigger.node.js", + "dist/nodes/Microsoft/SharePoint/MicrosoftSharePoint.node.js", "dist/nodes/Microsoft/Sql/MicrosoftSql.node.js", "dist/nodes/Microsoft/Storage/AzureStorage.node.js", "dist/nodes/Microsoft/Teams/MicrosoftTeams.node.js",