first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,428 @@
import type { INodeProperties } from 'n8n-workflow';
export const fileOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['file'],
},
},
options: [
{
name: 'Copy',
value: 'copy',
description: 'Copy a file',
action: 'Copy a file',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a file',
action: 'Delete a file',
},
{
name: 'Download',
value: 'download',
description: 'Download a file',
action: 'Download a file',
},
{
name: 'Get',
value: 'get',
description: 'Get a file',
action: 'Get a file',
},
{
name: 'Rename',
value: 'rename',
description: 'Rename a file',
action: 'Rename a file',
},
{
name: 'Search',
value: 'search',
description: 'Search a file',
action: 'Search a file',
},
{
name: 'Share',
value: 'share',
description: 'Share a file',
action: 'Share a file',
},
{
name: 'Upload',
value: 'upload',
description: 'Upload a file up to 4MB in size',
action: 'Upload a file',
},
],
default: 'upload',
},
];
export const fileFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* file:copy */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: ['copy'],
resource: ['file'],
},
},
default: '',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
operation: ['copy'],
resource: ['file'],
},
},
default: {},
options: [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description:
"The new name for the copy. If this isn't provided, the same name will be used as the original.",
},
],
},
{
displayName: 'Parent Reference',
name: 'parentReference',
type: 'collection',
placeholder: 'Add Parent Reference',
description:
'Reference to the parent item the copy will be created in <a href="https://docs.microsoft.com/en-us/onedrive/developer/rest-api/resources/itemreference?view=odsp-graph-online"> Details </a>',
displayOptions: {
show: {
operation: ['copy'],
resource: ['file'],
},
},
default: {},
options: [
{
displayName: 'Drive ID',
name: 'driveId',
type: 'string',
default: '',
description: 'Identifier of the drive instance that contains the item',
},
{
displayName: 'Drive Type',
name: 'driveType',
type: 'string',
default: '',
description: 'Identifies the type of drive',
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
description: 'Identifier of the item in the drive',
},
{
displayName: 'List ID',
name: 'listId',
type: 'string',
default: '',
description: 'Identifier of the list',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'The name of the item being referenced',
},
{
displayName: 'Path',
name: 'path',
type: 'string',
default: '',
description: 'Path that can be used to navigate to the item',
},
{
displayName: 'Share ID',
name: 'shareId',
type: 'string',
default: '',
description: 'Identifier for a shared resource that can be accessed via the Shares API',
},
{
displayName: 'Site ID',
name: 'siteId',
type: 'string',
default: '',
description: 'Identifier of the site',
},
],
},
/* -------------------------------------------------------------------------- */
/* file:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: ['delete'],
resource: ['file'],
},
},
default: '',
description: 'Field ID',
},
/* -------------------------------------------------------------------------- */
/* file:download */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: ['download'],
resource: ['file'],
},
},
default: '',
},
{
displayName: 'Put Output File in Field',
name: 'binaryPropertyName',
type: 'string',
required: true,
default: 'data',
displayOptions: {
show: {
operation: ['download'],
resource: ['file'],
},
},
hint: 'The name of the output binary field to put the file in',
},
/* -------------------------------------------------------------------------- */
/* file:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: ['get'],
resource: ['file'],
},
},
default: '',
description: 'Field ID',
},
/* -------------------------------------------------------------------------- */
/* file:rename */
/* -------------------------------------------------------------------------- */
{
displayName: 'Item ID',
name: 'itemId',
type: 'string',
displayOptions: {
show: {
operation: ['rename'],
resource: ['file'],
},
},
default: '',
description: 'ID of the file',
},
{
displayName: 'New Name',
name: 'newName',
type: 'string',
displayOptions: {
show: {
operation: ['rename'],
resource: ['file'],
},
},
default: '',
description: 'New name for file',
},
/* -------------------------------------------------------------------------- */
/* file:search */
/* -------------------------------------------------------------------------- */
{
displayName: 'Query',
name: 'query',
type: 'string',
displayOptions: {
show: {
operation: ['search'],
resource: ['file'],
},
},
default: '',
description:
'The query text used to search for items. Values may be matched across several fields including filename, metadata, and file content.',
},
/* -------------------------------------------------------------------------- */
/* file:share */
/* -------------------------------------------------------------------------- */
{
displayName: 'File ID',
name: 'fileId',
type: 'string',
displayOptions: {
show: {
operation: ['share'],
resource: ['file'],
},
},
default: '',
},
{
displayName: 'Type',
name: 'type',
type: 'options',
options: [
{
name: 'View',
value: 'view',
},
{
name: 'Edit',
value: 'edit',
},
{
name: 'Embed',
value: 'embed',
},
],
displayOptions: {
show: {
operation: ['share'],
resource: ['file'],
},
},
default: '',
description: 'The type of sharing link to create',
},
{
displayName: 'Scope',
name: 'scope',
type: 'options',
options: [
{
name: 'Anonymous',
value: 'anonymous',
},
{
name: 'Organization',
value: 'organization',
},
],
displayOptions: {
show: {
operation: ['share'],
resource: ['file'],
},
},
default: '',
description: 'The type of sharing link to create',
},
/* -------------------------------------------------------------------------- */
/* file:upload */
/* -------------------------------------------------------------------------- */
{
displayName: 'File Name',
name: 'fileName',
type: 'string',
displayOptions: {
show: {
operation: ['upload'],
resource: ['file'],
},
},
default: '',
description: 'The name the file should be saved as',
},
{
displayName: 'Parent ID',
name: 'parentId',
required: true,
type: 'string',
displayOptions: {
show: {
operation: ['upload'],
resource: ['file'],
},
},
default: '',
description: 'ID of the parent folder that will contain the file',
},
{
displayName: 'Binary File',
name: 'binaryData',
type: 'boolean',
default: false,
required: true,
displayOptions: {
show: {
operation: ['upload'],
resource: ['file'],
},
},
description: 'Whether the data to upload should be taken from binary field',
},
{
displayName: 'File Content',
name: 'fileContent',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
binaryData: [false],
operation: ['upload'],
resource: ['file'],
},
},
placeholder: '',
description: 'The text content of the file',
},
{
displayName: 'Input Binary Field',
name: 'binaryPropertyName',
type: 'string',
default: 'data',
required: true,
displayOptions: {
show: {
binaryData: [true],
operation: ['upload'],
resource: ['file'],
},
},
placeholder: '',
hint: 'The name of the input binary field containing the file to be written',
},
];
@@ -0,0 +1,224 @@
import type { INodeProperties } from 'n8n-workflow';
export const folderOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['folder'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a folder',
action: 'Create a folder',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a folder',
action: 'Delete a folder',
},
{
name: 'Get Children',
value: 'getChildren',
description: 'Get items inside a folder',
action: 'Get items in a folder',
},
{
name: 'Rename',
value: 'rename',
description: 'Rename a folder',
action: 'Rename a folder',
},
{
name: 'Search',
value: 'search',
description: 'Search a folder',
action: 'Search a folder',
},
{
name: 'Share',
value: 'share',
description: 'Share a folder',
action: 'Share a folder',
},
],
default: 'getChildren',
},
];
export const folderFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* folder:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Name',
name: 'name',
required: true,
type: 'string',
placeholder: '/Pictures/2021',
displayOptions: {
show: {
operation: ['create'],
resource: ['folder'],
},
},
default: '',
description: 'The name or path of the folder',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: ['create'],
resource: ['folder'],
},
},
default: {},
placeholder: 'Add Field',
options: [
{
displayName: 'Parent Folder ID',
name: 'parentFolderId',
type: 'string',
default: '',
description: 'ID of the folder you want to crate the new folder in',
},
],
},
/* -------------------------------------------------------------------------- */
/* folder:getChildren/delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Folder ID',
name: 'folderId',
type: 'string',
displayOptions: {
show: {
operation: ['delete', 'getChildren'],
resource: ['folder'],
},
},
default: '',
},
/* -------------------------------------------------------------------------- */
/* folder:rename */
/* -------------------------------------------------------------------------- */
{
displayName: 'Item ID',
name: 'itemId',
type: 'string',
displayOptions: {
show: {
operation: ['rename'],
resource: ['folder'],
},
},
default: '',
description: 'ID of the folder',
},
{
displayName: 'New Name',
name: 'newName',
type: 'string',
displayOptions: {
show: {
operation: ['rename'],
resource: ['folder'],
},
},
default: '',
description: 'New name for folder',
},
/* -------------------------------------------------------------------------- */
/* folder:search */
/* -------------------------------------------------------------------------- */
{
displayName: 'Query',
name: 'query',
type: 'string',
displayOptions: {
show: {
operation: ['search'],
resource: ['folder'],
},
},
default: '',
description:
'The query text used to search for items. Values may be matched across several fields including filename, metadata, and file content.',
},
/* -------------------------------------------------------------------------- */
/* folder:share */
/* -------------------------------------------------------------------------- */
{
displayName: 'Folder ID',
name: 'folderId',
type: 'string',
displayOptions: {
show: {
operation: ['share'],
resource: ['folder'],
},
},
default: '',
description: 'File ID',
},
{
displayName: 'Type',
name: 'type',
type: 'options',
options: [
{
name: 'View',
value: 'view',
},
{
name: 'Edit',
value: 'edit',
},
{
name: 'Embed',
value: 'embed',
},
],
displayOptions: {
show: {
operation: ['share'],
resource: ['folder'],
},
},
default: '',
description: 'The type of sharing link to create',
},
{
displayName: 'Scope',
name: 'scope',
type: 'options',
options: [
{
name: 'Anonymous',
value: 'anonymous',
},
{
name: 'Organization',
value: 'organization',
},
],
displayOptions: {
show: {
operation: ['share'],
resource: ['folder'],
},
},
default: '',
description: 'The type of sharing link to create',
},
];
@@ -0,0 +1,185 @@
import { DateTime } from 'luxon';
import type {
IExecuteFunctions,
ILoadOptionsFunctions,
IDataObject,
JsonObject,
IHttpRequestMethods,
IRequestOptions,
IPollFunctions,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
export async function microsoftApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
method: IHttpRequestMethods,
resource: string,
body: any = {},
qs: IDataObject = {},
uri?: string,
headers: IDataObject = {},
option: IDataObject = { json: true },
): Promise<any> {
const credentials = await this.getCredentials('microsoftOneDriveOAuth2Api');
const baseUrl = (
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
? credentials.graphApiBaseUrl
: 'https://graph.microsoft.com'
).replace(/\/+$/, '');
const options: IRequestOptions = {
headers: {
'Content-Type': 'application/json',
},
method,
body,
qs,
uri: uri || `${baseUrl}/v1.0/me${resource}`,
};
try {
Object.assign(options, option);
if (Object.keys(headers).length !== 0) {
options.headers = Object.assign({}, options.headers, headers);
}
if (Object.keys(qs).length === 0) {
delete options.qs;
}
if (Object.keys(body as IDataObject).length === 0) {
delete options.body;
}
return await this.helpers.requestOAuth2.call(this, 'microsoftOneDriveOAuth2Api', options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}
}
export async function microsoftApiRequestAllItems(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
propertyName: string,
method: IHttpRequestMethods,
endpoint: string,
body: any = {},
query: IDataObject = {},
): Promise<any> {
const returnData: IDataObject[] = [];
let responseData;
let uri: string | undefined;
query.$top = 100;
do {
responseData = await microsoftApiRequest.call(this, method, endpoint, body, query, uri);
uri = responseData['@odata.nextLink'];
if (uri?.includes('$top')) {
delete query.$top;
}
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
} while (responseData['@odata.nextLink'] !== undefined);
return returnData;
}
export async function microsoftApiRequestAllItemsSkip(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
propertyName: string,
method: IHttpRequestMethods,
endpoint: string,
body: any = {},
query: IDataObject = {},
): Promise<any> {
const returnData: IDataObject[] = [];
let responseData;
query.$top = 100;
query.$skip = 0;
do {
responseData = await microsoftApiRequest.call(this, method, endpoint, body, query);
query.$skip += query.$top;
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
} while (responseData.value.length !== 0);
return returnData;
}
export async function microsoftApiRequestAllItemsDelta(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
link: string,
lastDate: DateTime,
eventType: string,
): Promise<any> {
const returnData: IDataObject[] = [];
let responseData;
let deltaLink: string = '';
let uri: string = link;
do {
responseData = (await microsoftApiRequest.call(this, 'GET', '', {}, {}, uri)) as IDataObject;
uri = responseData['@odata.nextLink'] as string;
for (const value of responseData.value as IDataObject[]) {
if (value.fileSystemInfo as IDataObject) {
const updatedTimeStamp = (value.fileSystemInfo as IDataObject)
?.lastModifiedDateTime as string;
const createdTimeStamp = (value.fileSystemInfo as IDataObject)?.createdDateTime as string;
if (eventType === 'created') {
if (DateTime.fromISO(createdTimeStamp) >= lastDate) {
returnData.push(value);
}
}
if (eventType === 'updated') {
if (
DateTime.fromISO(updatedTimeStamp) >= lastDate &&
DateTime.fromISO(createdTimeStamp) < lastDate
) {
returnData.push(value);
}
}
}
}
//returnData.push.apply(returnData, responseData.value as IDataObject[]);
deltaLink = (responseData['@odata.deltaLink'] as string) || '';
} while (responseData['@odata.nextLink'] !== undefined);
return { deltaLink, returnData };
}
export async function getPath(
this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions,
itemId: string,
): Promise<string> {
const credentials = await this.getCredentials('microsoftOneDriveOAuth2Api');
const baseUrl = (
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
? credentials.graphApiBaseUrl
: 'https://graph.microsoft.com'
).replace(/\/+$/, '');
const responseData = (await microsoftApiRequest.call(
this,
'GET',
'',
{},
{},
`${baseUrl}/v1.0/me/drive/items/${itemId}`,
)) as IDataObject;
if (responseData.folder) {
return (responseData?.parentReference as IDataObject)?.path + `/${responseData?.name}`;
} else {
const workflow = this.getWorkflow();
const node = this.getNode();
this.logger.error(
`There was a problem in '${node.name}' node in workflow '${workflow.id}': 'Item to watch is not a folder'`,
{
node: node.name,
workflowId: workflow.id,
error: 'Item to watch is not a folder',
},
);
throw new NodeApiError(this.getNode(), {
error: 'Item to watch is not a folder',
} as JsonObject);
}
}
@@ -0,0 +1,25 @@
{
"node": "n8n-nodes-base.microsoftOneDrive",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Data & Storage"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftonedrive/"
}
],
"generic": [
{
"label": "Hey founders! Your business doesn't need you to operate",
"icon": " 🖥️",
"url": "https://n8n.io/blog/your-business-doesnt-need-you-to-operate/"
}
]
}
}
@@ -0,0 +1,384 @@
import { IncomingMessage } from 'http';
import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
import { fileFields, fileOperations } from './FileDescription';
import { folderFields, folderOperations } from './FolderDescription';
import { microsoftApiRequest, microsoftApiRequestAllItems } from './GenericFunctions';
export class MicrosoftOneDrive implements INodeType {
description: INodeTypeDescription = {
displayName: 'Microsoft OneDrive',
name: 'microsoftOneDrive',
icon: 'file:oneDrive.svg',
group: ['input'],
version: [1, 1.1],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Microsoft OneDrive API',
schemaPath: 'Microsoft/OneDrive',
defaults: {
name: 'Microsoft OneDrive',
},
usableAsTool: true,
inputs: [NodeConnectionTypes.Main],
outputs: [NodeConnectionTypes.Main],
credentials: [
{
name: 'microsoftOneDriveOAuth2Api',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'File',
value: 'file',
},
{
name: 'Folder',
value: 'folder',
},
],
default: 'file',
},
...fileOperations,
...fileFields,
...folderOperations,
...folderFields,
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const length = items.length;
const nodeVersion = this.getNode().typeVersion;
let responseData;
const resource = this.getNodeParameter('resource', 0);
const operation = this.getNodeParameter('operation', 0);
for (let i = 0; i < length; i++) {
try {
if (resource === 'file') {
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_copy?view=odsp-graph-online
if (operation === 'copy') {
const fileId = this.getNodeParameter('fileId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i);
const parentReference = this.getNodeParameter('parentReference', i) as IDataObject;
const body: IDataObject = {};
if (parentReference) {
body.parentReference = { ...parentReference };
}
if (additionalFields.name) {
body.name = additionalFields.name as string;
}
responseData = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${fileId}/copy`,
body,
{},
undefined,
{},
{ json: true, resolveWithFullResponse: true },
);
responseData = { location: responseData.headers.location };
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online
if (operation === 'delete') {
const fileId = this.getNodeParameter('fileId', i) as string;
responseData = await microsoftApiRequest.call(this, 'DELETE', `/drive/items/${fileId}`);
responseData = { success: true };
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online
if (operation === 'download') {
const fileId = this.getNodeParameter('fileId', i) as string;
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i);
responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`);
const fileName = responseData.name;
const downloadUrl = responseData['@microsoft.graph.downloadUrl'];
if (responseData.file === undefined) {
throw new NodeApiError(this.getNode(), responseData as JsonObject, {
message: 'The ID you provided does not belong to a file.',
});
}
let mimeType: string | undefined;
if (responseData.file.mimeType) {
mimeType = responseData.file.mimeType;
}
try {
responseData = await microsoftApiRequest.call(
this,
'GET',
`/drive/items/${fileId}/content`,
{},
{},
undefined,
{},
{ encoding: null, resolveWithFullResponse: true },
);
} catch (error) {
if (downloadUrl) {
responseData = await this.helpers.httpRequest({
method: 'GET',
url: downloadUrl,
returnFullResponse: true,
encoding: 'arraybuffer',
json: false,
});
}
}
const newItem: INodeExecutionData = {
json: items[i].json,
binary: {},
};
if (mimeType === undefined && responseData.headers['content-type']) {
mimeType = responseData.headers['content-type'];
}
if (items[i].binary !== undefined) {
// Create a shallow copy of the binary data so that the old
// data references which do not get changed still stay behind
// but the incoming data does not get changed.
Object.assign(newItem.binary!, items[i].binary);
}
items[i] = newItem;
let data;
if (responseData?.body instanceof IncomingMessage) {
data = responseData.body;
} else {
data = Buffer.from(responseData.body as Buffer);
}
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
data,
fileName as string,
mimeType,
);
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get?view=odsp-graph-online
if (operation === 'get') {
const fileId = this.getNodeParameter('fileId', i) as string;
responseData = await microsoftApiRequest.call(this, 'GET', `/drive/items/${fileId}`);
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online
if (operation === 'search') {
const query = this.getNodeParameter('query', i) as string;
responseData = await microsoftApiRequestAllItems.call(
this,
'value',
'GET',
`/drive/root/search(q='${query}')`,
);
responseData = responseData.filter((item: IDataObject) => item.file);
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createlink?view=odsp-graph-online
if (operation === 'share') {
const fileId = this.getNodeParameter('fileId', i) as string;
const type = this.getNodeParameter('type', i) as string;
const scope = this.getNodeParameter('scope', i) as string;
const body: IDataObject = {
type,
scope,
};
responseData = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${fileId}/createLink`,
body,
);
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online#example-upload-a-new-file
if (operation === 'upload') {
const parentId = this.getNodeParameter('parentId', i) as string;
const isBinaryData = this.getNodeParameter('binaryData', i);
const fileName = this.getNodeParameter('fileName', i) as string;
if (isBinaryData) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0);
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
const body = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
let encodedFilename;
if (nodeVersion >= 1.1) {
if (fileName !== '') {
encodedFilename = encodeURIComponent(fileName);
} else if (binaryData.fileName !== undefined) {
encodedFilename = encodeURIComponent(binaryData.fileName);
}
} else {
if (fileName !== '') {
encodedFilename = encodeURIComponent(fileName);
}
if (binaryData.fileName !== undefined) {
encodedFilename = encodeURIComponent(binaryData.fileName);
}
}
responseData = await microsoftApiRequest.call(
this,
'PUT',
`/drive/items/${parentId}:/${encodedFilename}:/content`,
body,
{},
undefined,
{ 'Content-Type': binaryData.mimeType, 'Content-length': body.length },
{},
);
responseData = JSON.parse(responseData as string);
} else {
const body = this.getNodeParameter('fileContent', i) as string;
if (fileName === '') {
throw new NodeOperationError(this.getNode(), 'File name must be set!', {
itemIndex: i,
});
}
const encodedFilename = encodeURIComponent(fileName);
responseData = await microsoftApiRequest.call(
this,
'PUT',
`/drive/items/${parentId}:/${encodedFilename}:/content`,
body,
{},
undefined,
{ 'Content-Type': 'text/plain' },
);
}
}
}
if (resource === 'folder') {
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online
if (operation === 'create') {
const names = (this.getNodeParameter('name', i) as string)
.split('/')
.filter((s) => s.trim() !== '');
const options = this.getNodeParameter('options', i);
let parentFolderId = options.parentFolderId ? options.parentFolderId : null;
for (const name of names) {
const body: IDataObject = {
name,
folder: {},
};
let endpoint = '/drive/root/children';
if (parentFolderId) {
endpoint = `/drive/items/${parentFolderId}/children`;
}
responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
if (!responseData.id) {
break;
}
parentFolderId = responseData.id;
}
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete?view=odsp-graph-online
if (operation === 'delete') {
const folderId = this.getNodeParameter('folderId', i) as string;
responseData = await microsoftApiRequest.call(
this,
'DELETE',
`/drive/items/${folderId}`,
);
responseData = { success: true };
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children?view=odsp-graph-online
if (operation === 'getChildren') {
const folderId = this.getNodeParameter('folderId', i) as string;
responseData = await microsoftApiRequestAllItems.call(
this,
'value',
'GET',
`/drive/items/${folderId}/children`,
);
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search?view=odsp-graph-online
if (operation === 'search') {
const query = this.getNodeParameter('query', i) as string;
responseData = await microsoftApiRequestAllItems.call(
this,
'value',
'GET',
`/drive/root/search(q='${query}')`,
);
responseData = responseData.filter((item: IDataObject) => item.folder);
}
//https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createlink?view=odsp-graph-online
if (operation === 'share') {
const folderId = this.getNodeParameter('folderId', i) as string;
const type = this.getNodeParameter('type', i) as string;
const scope = this.getNodeParameter('scope', i) as string;
const body: IDataObject = {
type,
scope,
};
responseData = await microsoftApiRequest.call(
this,
'POST',
`/drive/items/${folderId}/createLink`,
body,
);
}
}
if (resource === 'file' || resource === 'folder') {
if (operation === 'rename') {
const itemId = this.getNodeParameter('itemId', i) as string;
const newName = this.getNodeParameter('newName', i) as string;
const body = { name: newName };
responseData = await microsoftApiRequest.call(
this,
'PATCH',
`/drive/items/${itemId}`,
body,
);
}
}
} catch (error) {
if (this.continueOnFail()) {
if (resource === 'file' && operation === 'download') {
items[i].json = { error: error.message };
} else {
const executionErrorData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray({ error: error.message }),
{ itemData: { item: i } },
);
returnData.push(...executionErrorData);
}
continue;
}
throw error;
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject),
{ itemData: { item: i } },
);
returnData.push(...executionData);
}
if (resource === 'file' && operation === 'download') {
// For file downloads the files get attached to the existing items
return [items];
}
return [returnData];
}
}
@@ -0,0 +1,25 @@
{
"node": "n8n-nodes-base.microsoftOneDriveTrigger",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Data & Storage"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.microsoftonedrivetrigger/"
}
],
"generic": [
{
"label": "Hey founders! Your business doesn't need you to operate",
"icon": " 🖥️",
"url": "https://n8n.io/blog/your-business-doesnt-need-you-to-operate/"
}
]
}
}
@@ -0,0 +1,178 @@
import { DateTime } from 'luxon';
import {
type IPollFunctions,
type IDataObject,
type INodeExecutionData,
type INodeType,
type INodeTypeDescription,
NodeConnectionTypes,
} from 'n8n-workflow';
import { getPath, microsoftApiRequest, microsoftApiRequestAllItemsDelta } from './GenericFunctions';
import { triggerDescription } from './TriggerDescription';
export class MicrosoftOneDriveTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Microsoft OneDrive Trigger',
name: 'microsoftOneDriveTrigger',
icon: 'file:oneDrive.svg',
group: ['trigger'],
version: 1,
description: 'Trigger for Microsoft OneDrive API.',
subtitle: '={{($parameter["event"])}}',
defaults: {
name: 'Microsoft OneDrive Trigger',
},
credentials: [
{
name: 'microsoftOneDriveOAuth2Api',
required: true,
},
],
polling: true,
inputs: [],
outputs: [NodeConnectionTypes.Main],
properties: [...triggerDescription],
};
methods = {
loadOptions: {},
};
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const workflowData = this.getWorkflowStaticData('node');
let responseData: IDataObject[];
const credentials = await this.getCredentials('microsoftOneDriveOAuth2Api');
const baseUrl = (
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
? credentials.graphApiBaseUrl
: 'https://graph.microsoft.com'
).replace(/\/+$/, '');
const lastLink: string =
(workflowData.LastLink as string) || `${baseUrl}/v1.0/me/drive/root/delta?token=latest`;
const now = DateTime.now().toUTC();
const start = DateTime.fromISO(workflowData.lastTimeChecked as string) || now;
const end = now;
const event = this.getNodeParameter('event', 'fileCreated') as string;
const watch = this.getNodeParameter('watch', 'anyFile') as string;
const watchFolder = (this.getNodeParameter('watchFolder', false) as boolean) || false;
const folderChild = (this.getNodeParameter('options.folderChild', false) as boolean) || false;
let eventType = 'created';
let eventResource = 'file';
if (event.includes('Updated')) {
eventType = 'updated';
}
if (event.includes('folder')) {
eventResource = 'folder';
}
try {
if (this.getMode() === 'manual') {
responseData = (
await microsoftApiRequest.call(
this,
'GET',
'',
{},
{},
`${baseUrl}/v1.0/me/drive/root/delta`,
)
).value as IDataObject[];
} else {
const response: IDataObject = (await microsoftApiRequestAllItemsDelta.call(
this,
lastLink,
start,
eventType,
)) as IDataObject;
responseData = response.returnData as IDataObject[];
workflowData.LastLink = response.deltaLink;
}
workflowData.lastTimeChecked = end.toISO();
if (watch === 'selectedFile') {
const fileId = (
this.getNodeParameter('fileId', '', {
extractValue: true,
}) as string
).replace('%21', '!');
if (fileId) {
responseData = responseData.filter((item: IDataObject) => item.id === fileId);
}
}
if (
!folderChild &&
(watch === 'oneSelectedFolder' || watch === 'selectedFolder' || watchFolder)
) {
const folderId = (
this.getNodeParameter('folderId', '', {
extractValue: true,
}) as string
).replace('%21', '!');
if (folderId) {
if (watch === 'oneSelectedFolder') {
responseData = responseData.filter((item: IDataObject) => item.id === folderId);
} else {
responseData = responseData.filter(
(item: IDataObject) => (item.parentReference as IDataObject).id === folderId,
);
}
}
}
if (folderChild && (watch === 'selectedFolder' || watchFolder)) {
const folderId = (
this.getNodeParameter('folderId', '', {
extractValue: true,
}) as string
).replace('%21', '!');
const folderPath = await getPath.call(this, folderId);
responseData = responseData.filter((item: IDataObject) => {
const path = (item.parentReference as IDataObject)?.path as string;
return typeof path === 'string' && path.startsWith(folderPath);
});
}
responseData = responseData.filter((item: IDataObject) => item[eventResource]);
if (!responseData?.length) {
return null;
}
const simplify = this.getNodeParameter('simple') as boolean;
if (simplify) {
responseData = responseData.map((x) => ({
id: x.id,
createdDateTime: (x.fileSystemInfo as IDataObject)?.createdDateTime,
lastModifiedDateTime: (x.fileSystemInfo as IDataObject)?.lastModifiedDateTime,
name: x.name,
webUrl: x.webUrl,
size: x.size,
path: (x.parentReference as IDataObject)?.path || '',
mimeType: (x.file as IDataObject)?.mimeType || '',
}));
}
return [this.helpers.returnJsonArray(responseData)];
} catch (error) {
if (this.getMode() === 'manual' || !workflowData.lastTimeChecked) {
throw error;
}
const workflow = this.getWorkflow();
const node = this.getNode();
this.logger.error(
`There was a problem in '${node.name}' node in workflow '${workflow.id}': '${error.description}'`,
{
node: node.name,
workflowId: workflow.id,
error,
},
);
throw error;
}
return null;
}
}
@@ -0,0 +1,289 @@
import type { INodeProperties } from 'n8n-workflow';
import { MICROSOFT_DRIVE_FILE_URL_REGEX, MICROSOFT_DRIVE_FOLDER_URL_REGEX } from './constants';
export const fileRLC: INodeProperties = {
displayName: 'File',
name: 'fileId',
type: 'resourceLocator',
default: { mode: 'id', value: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder:
'e.g. https://onedrive.live.com/edit.aspx?resid=170B5C65E30736A3!257&cid=170b5c65e30736a3&CT=1708697995542&OR=ItemsView',
extractValue: {
type: 'regex',
regex: MICROSOFT_DRIVE_FILE_URL_REGEX,
},
validation: [
{
type: 'regex',
properties: {
regex: MICROSOFT_DRIVE_FILE_URL_REGEX,
errorMessage: 'Not a valid Microsoft Drive File URL',
},
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'e.g. 170B5C65E30736A3!257',
validation: [
{
type: 'regex',
properties: {
regex: '[a-zA-Z0-9\\!%21]{5,}',
errorMessage: 'Not a valid Microsoft Drive File ID',
},
},
],
url: '=https://onedrive.live.com/?id={{$value}}',
},
],
description:
"The file to operate on. The 'By URL' option only accepts URLs that start with 'https://onedrive.live.com'.",
};
export const folderRLC: INodeProperties = {
displayName: 'Folder',
name: 'folderId',
type: 'resourceLocator',
default: { mode: 'id', value: '', cachedResultName: '' },
required: true,
modes: [
{
displayName: 'Link',
name: 'url',
type: 'string',
placeholder: 'e.g. https://onedrive.live.com/?id=170B5C65E30736A3%21103&cid=170B5C65E30736A3',
extractValue: {
type: 'regex',
regex: MICROSOFT_DRIVE_FOLDER_URL_REGEX,
},
validation: [
{
type: 'regex',
properties: {
regex: MICROSOFT_DRIVE_FOLDER_URL_REGEX,
errorMessage: 'Not a valid Microsoft Drive Folder URL',
},
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
placeholder: 'e.g. 170B5C65E30736A3%21136',
validation: [
{
type: 'regex',
properties: {
regex: '[a-zA-Z0-9\\!%21]{5,}',
errorMessage: 'Not a valid Microsoft Drive Folder ID',
},
},
],
url: '=https://onedrive.live.com/?id={{$value}}',
},
],
description:
"The folder to operate on. The 'By URL' option only accepts URLs that start with 'https://onedrive.live.com'.",
};
export const triggerDescription: INodeProperties[] = [
{
displayName: 'Trigger On',
name: 'event',
type: 'options',
default: 'fileCreated',
options: [
{
name: 'File Created',
value: 'fileCreated',
description: 'When a new file is created',
},
{
name: 'File Updated',
value: 'fileUpdated',
description: 'When an existing file is modified',
},
{
name: 'Folder Created',
value: 'folderCreated',
description: 'When a new folder is created',
},
{
name: 'Folder Updated',
value: 'folderUpdated',
description: 'When an existing folder is modified',
},
],
},
{
displayName: 'Simplify',
name: 'simple',
description: 'Whether to return a simplified version of the response instead of the raw data',
type: 'boolean',
default: true,
},
{
displayName: 'Watch Folder',
name: 'watchFolder',
description:
'Whether to watch for the created file in a given folder, rather than the entire OneDrive',
type: 'boolean',
default: false,
displayOptions: {
show: {
event: ['fileCreated'],
},
},
},
{
displayName: 'Watch',
name: 'watch',
description: 'How to select which file to watch',
type: 'options',
default: 'anyFile',
displayOptions: {
show: {
event: ['fileUpdated'],
},
},
options: [
{
name: 'Any File',
value: 'anyFile',
description: 'Watch for updated files in the entire OneDrive',
},
{
name: 'Inside a Folder',
value: 'selectedFolder',
description: 'Watch for updated files inside a selected folder',
},
{
name: 'A Selected File',
value: 'selectedFile',
description: 'Watch a specific file for updates',
},
],
},
{
displayName: 'Watch Folder',
name: 'watchFolder',
description:
'Whether to watch for the created folder in a given folder, rather than the entire OneDrive',
type: 'boolean',
default: false,
displayOptions: {
show: {
event: ['folderCreated'],
},
},
},
{
displayName: 'Watch',
name: 'watch',
description: 'How to select which folder to watch',
type: 'options',
default: 'anyFolder',
displayOptions: {
show: {
event: ['folderUpdated'],
},
},
options: [
{
name: 'Any Folder',
value: 'anyFolder',
description: 'Watch for updated folders in the entire OneDrive',
},
{
name: 'Inside a Folder',
value: 'selectedFolder',
description: 'Watch for updated folders inside a selected folder',
},
{
name: 'A Selected Folder',
value: 'oneSelectedFolder',
description: 'Watch a specific folder for updates',
},
],
},
{
...fileRLC,
displayOptions: {
show: {
event: ['fileUpdated'],
watch: ['selectedFile'],
},
},
},
{
...folderRLC,
displayOptions: {
show: {
watch: ['selectedFolder', 'oneSelectedFolder'],
},
},
},
{
...folderRLC,
displayOptions: {
show: {
watchFolder: [true],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
watch: ['selectedFolder'],
},
},
options: [
{
displayName: 'Watch Nested Folders',
name: 'folderChild',
type: 'boolean',
default: false,
description:
'Whether to look for modified files/folders in all nested folders, rather than only direct descendants',
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add option',
default: {},
displayOptions: {
show: {
watchFolder: [true],
},
},
options: [
{
displayName: 'Watch Nested Folders',
name: 'folderChild',
type: 'boolean',
default: false,
description:
'Whether to look for modified files/folders in all nested folders, rather than only direct descendants',
},
],
},
];
@@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"location": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
},
"version": 1
}
@@ -0,0 +1,112 @@
{
"type": "object",
"properties": {
"createdBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"file": {
"type": "object",
"properties": {
"hashes": {
"type": "object",
"properties": {
"quickXorHash": {
"type": "string"
}
}
},
"mimeType": {
"type": "string"
}
}
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 7
}
@@ -0,0 +1,143 @@
{
"type": "object",
"properties": {
"@microsoft.graph.downloadUrl": {
"type": "string"
},
"@odata.context": {
"type": "string"
},
"createdBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"cTag": {
"type": "string"
},
"eTag": {
"type": "string"
},
"file": {
"type": "object",
"properties": {
"hashes": {
"type": "object",
"properties": {
"quickXorHash": {
"type": "string"
}
}
},
"mimeType": {
"type": "string"
}
}
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"shared": {
"type": "object",
"properties": {
"scope": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,146 @@
{
"type": "object",
"properties": {
"@microsoft.graph.downloadUrl": {
"type": "string"
},
"@odata.context": {
"type": "string"
},
"createdBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"cTag": {
"type": "string"
},
"eTag": {
"type": "string"
},
"file": {
"type": "object",
"properties": {
"hashes": {
"type": "object",
"properties": {
"quickXorHash": {
"type": "string"
}
}
},
"mimeType": {
"type": "string"
}
}
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,92 @@
{
"type": "object",
"properties": {
"createdBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"file": {
"type": "object",
"properties": {
"mimeType": {
"type": "string"
}
}
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,44 @@
{
"type": "object",
"properties": {
"@odata.context": {
"type": "string"
},
"hasPassword": {
"type": "boolean"
},
"id": {
"type": "string"
},
"link": {
"type": "object",
"properties": {
"preventsDownload": {
"type": "boolean"
},
"scope": {
"type": "string"
},
"type": {
"type": "string"
},
"webHtml": {
"type": "string"
},
"webUrl": {
"type": "string"
}
}
},
"roles": {
"type": "array",
"items": {
"type": "string"
}
},
"shareId": {
"type": "string"
}
},
"version": 2
}
@@ -0,0 +1,146 @@
{
"type": "object",
"properties": {
"@microsoft.graph.downloadUrl": {
"type": "string"
},
"@odata.context": {
"type": "string"
},
"createdBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"cTag": {
"type": "string"
},
"eTag": {
"type": "string"
},
"file": {
"type": "object",
"properties": {
"hashes": {
"type": "object",
"properties": {
"quickXorHash": {
"type": "string"
}
}
},
"mimeType": {
"type": "string"
}
}
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,182 @@
{
"type": "object",
"properties": {
"@odata.context": {
"type": "string"
},
"@odata.etag": {
"type": "string"
},
"commentSettings": {
"type": "object",
"properties": {
"commentingDisabled": {
"type": "object",
"properties": {
"isDisabled": {
"type": "boolean"
}
}
}
}
},
"createdBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"cTag": {
"type": "string"
},
"eTag": {
"type": "string"
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"folder": {
"type": "object",
"properties": {
"childCount": {
"type": "integer"
},
"view": {
"type": "object",
"properties": {
"sortBy": {
"type": "string"
},
"sortOrder": {
"type": "string"
},
"viewType": {
"type": "string"
}
}
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"path": {
"type": "string"
},
"sharepointIds": {
"type": "object",
"properties": {
"listId": {
"type": "string"
},
"listItemUniqueId": {
"type": "string"
},
"siteId": {
"type": "string"
},
"siteUrl": {
"type": "string"
},
"tenantId": {
"type": "string"
},
"webId": {
"type": "string"
}
}
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 5
}
@@ -0,0 +1,140 @@
{
"type": "object",
"properties": {
"@microsoft.graph.downloadUrl": {
"type": "string"
},
"createdBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"cTag": {
"type": "string"
},
"eTag": {
"type": "string"
},
"file": {
"type": "object",
"properties": {
"hashes": {
"type": "object",
"properties": {
"quickXorHash": {
"type": "string"
}
}
},
"mimeType": {
"type": "string"
}
}
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"application": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"id": {
"type": "string"
}
}
},
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"id": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"shared": {
"type": "object",
"properties": {
"scope": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,92 @@
{
"type": "object",
"properties": {
"createdBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
}
}
}
}
},
"createdDateTime": {
"type": "string"
},
"fileSystemInfo": {
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
}
}
},
"folder": {
"type": "object",
"properties": {
"childCount": {
"type": "integer"
}
}
},
"id": {
"type": "string"
},
"lastModifiedBy": {
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"displayName": {
"type": "string"
},
"email": {
"type": "string"
}
}
}
}
},
"lastModifiedDateTime": {
"type": "string"
},
"name": {
"type": "string"
},
"parentReference": {
"type": "object",
"properties": {
"driveId": {
"type": "string"
},
"driveType": {
"type": "string"
},
"id": {
"type": "string"
},
"siteId": {
"type": "string"
}
}
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,41 @@
{
"type": "object",
"properties": {
"@odata.context": {
"type": "string"
},
"hasPassword": {
"type": "boolean"
},
"id": {
"type": "string"
},
"link": {
"type": "object",
"properties": {
"preventsDownload": {
"type": "boolean"
},
"scope": {
"type": "string"
},
"type": {
"type": "string"
},
"webUrl": {
"type": "string"
}
}
},
"roles": {
"type": "array",
"items": {
"type": "string"
}
},
"shareId": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,30 @@
{
"type": "object",
"properties": {
"createdDateTime": {
"type": "string"
},
"id": {
"type": "string"
},
"lastModifiedDateTime": {
"type": "string"
},
"mimeType": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"size": {
"type": "integer"
},
"webUrl": {
"type": "string"
}
},
"version": 1
}
@@ -0,0 +1,5 @@
export const MICROSOFT_DRIVE_FILE_URL_REGEX =
'https:\\/\\/onedrive.live.com(?:\\/.*?(?:\\&|\\?)(?:id=|resid=))(.+?)(?:\\&.*)';
export const MICROSOFT_DRIVE_FOLDER_URL_REGEX =
'https:\\/\\/onedrive.live.com(?:\\/.*id=)(.+)(?:\\&cid.*)';
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 81 81"><use xlink:href="#a" x=".5" y=".5"/><symbol id="a" overflow="visible"><g stroke="none"><path fill="#094ab2" d="M6.667 0Q0 0 0 6.667v66.667Q0 80 6.667 80h66.667Q80 80 80 73.333V6.667Q80 0 73.333 0z"/><path d="M60.256 42.023c2.87.405 4.965 2.975 4.832 5.929 0 2.594-1.421 5.655-4.832 5.655H32.574c-5.306 0-7.232-3.706-7.232-7.524 0-6.444 6.979-6.621 6.979-6.621s.569-6.992 6.664-8.41c3.806-1.113 7.875.477 9.98 3.899a8.2 8.2 0 0 1 7.058-.258c2.812 1.257 4.518 4.212 4.232 7.33z"/><path fill="#f9f9f9" d="M23.583 45.605c0-7.082 7.412-8.139 7.412-8.139.707-4.108 3.699-7.435 7.682-8.54 4.021-1.286 8.404.189 10.856 3.653a6.3 6.3 0 0 1 3.635-.737c-.513-4.177-3.247-7.736-7.126-9.277-5.141-2.153-11.058.026-13.618 5.015a8.4 8.4 0 0 0-8.126-.16c-3.181 1.763-5.002 5.282-4.619 8.924-3.675.323-6.438 3.523-6.254 7.242.114 3.817 3.139 6.892 6.92 7.034h4.65a9.1 9.1 0 0 1-1.413-5.015z"/><path fill-opacity=".19" d="m.84 2.69 77.753 75.816A4.4 4.4 0 0 0 80 75.279V4.398A4.39 4.39 0 0 0 75.612 0H4.885A4.39 4.39 0 0 0 .84 2.69"/></g></symbol></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,295 @@
import { mockDeep } from 'jest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import { microsoftApiRequest, getPath } from '../GenericFunctions';
describe('Microsoft OneDrive GenericFunctions', () => {
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
let mockNode: INode;
let mockRequestOAuth2: jest.Mock;
beforeEach(() => {
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
mockRequestOAuth2 = jest.fn();
mockExecuteFunctions.helpers.requestOAuth2 = mockRequestOAuth2;
mockNode = {
id: 'test-node',
name: 'Test OneDrive Node',
type: 'n8n-nodes-base.microsoftOneDrive',
typeVersion: 1,
position: [0, 0],
parameters: {},
};
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
mockExecuteFunctions.getWorkflow.mockReturnValue({
id: 'test-workflow-id',
} as any);
jest.clearAllMocks();
});
afterEach(() => {
jest.resetAllMocks();
});
describe('microsoftApiRequest', () => {
describe('graphApiBaseUrl from credentials', () => {
it('should use base URL from credentials', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://graph.microsoft.us',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.us/v1.0/me/drive',
json: true,
}),
);
});
it('should fall back to default when credentials.graphApiBaseUrl is empty', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: '',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.com/v1.0/me/drive',
json: true,
}),
);
});
it('should fall back to default when credentials.graphApiBaseUrl is undefined', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.com/v1.0/me/drive',
json: true,
}),
);
});
it('should strip trailing slashes from base URL using regex', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://graph.microsoft.com/',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.com/v1.0/me/drive',
json: true,
}),
);
});
it('should strip multiple trailing slashes from base URL', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://graph.microsoft.com///',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.com/v1.0/me/drive',
json: true,
}),
);
});
it('should use US Government cloud endpoint', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://graph.microsoft.us',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.us/v1.0/me/drive',
json: true,
}),
);
});
it('should use US Government DOD cloud endpoint', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://dod-graph.microsoft.us',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://dod-graph.microsoft.us/v1.0/me/drive',
json: true,
}),
);
});
it('should use China cloud endpoint', async () => {
const mockResponse = { data: 'test' };
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://microsoftgraph.chinacloudapi.cn',
});
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/drive');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://microsoftgraph.chinacloudapi.cn/v1.0/me/drive',
json: true,
}),
);
});
});
});
describe('getPath', () => {
describe('graphApiBaseUrl from credentials', () => {
it('should use base URL from credentials', async () => {
const mockResponse = {
name: 'test-item',
folder: {},
parentReference: { path: '/drive/root:/folder' },
};
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://graph.microsoft.us',
});
await getPath.call(mockExecuteFunctions, 'item-id-123');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.us/v1.0/me/drive/items/item-id-123',
json: true,
}),
);
});
it('should fall back to default when credentials.graphApiBaseUrl is empty', async () => {
const mockResponse = {
name: 'test-item',
folder: {},
parentReference: { path: '/drive/root:/folder' },
};
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: '',
});
await getPath.call(mockExecuteFunctions, 'item-id-123');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.com/v1.0/me/drive/items/item-id-123',
json: true,
}),
);
});
it('should strip trailing slashes from base URL using regex', async () => {
const mockResponse = {
name: 'test-item',
folder: {},
parentReference: { path: '/drive/root:/folder' },
};
mockRequestOAuth2.mockResolvedValue(mockResponse);
mockExecuteFunctions.getCredentials.mockResolvedValue({
oauthTokenData: {
access_token: 'test-access-token',
},
graphApiBaseUrl: 'https://graph.microsoft.com/',
});
await getPath.call(mockExecuteFunctions, 'item-id-123');
expect(mockRequestOAuth2).toHaveBeenCalledWith(
'microsoftOneDriveOAuth2Api',
expect.objectContaining({
method: 'GET',
uri: 'https://graph.microsoft.com/v1.0/me/drive/items/item-id-123',
json: true,
}),
);
});
});
});
});
@@ -0,0 +1,86 @@
import type { IncomingMessage } from 'http';
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import { type IHttpRequestMethods, type IExecuteFunctions, ApplicationError } from 'n8n-workflow';
import * as genericFunctions from '../../GenericFunctions';
import { MicrosoftOneDrive } from '../../MicrosoftOneDrive.node';
jest.mock('../../GenericFunctions', () => ({
...jest.requireActual('../../GenericFunctions'),
microsoftApiRequest: jest.fn(async function (_: IHttpRequestMethods, resource: string) {
if (resource === '/drive/items/fileID') {
return {
name: 'MyFile',
'@microsoft.graph.downloadUrl': 'https://test.com/file',
file: {
mimeType: 'image/png',
},
};
}
if (resource === '/drive/items/fileID/content') {
throw new ApplicationError('Error');
}
}),
}));
describe('Test MicrosoftOneDrive, file > download', () => {
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
let microsoftOneDrive: MicrosoftOneDrive;
const httpRequest = jest.fn(async () => ({ body: mock<IncomingMessage>() }));
const prepareBinaryData = jest.fn(async () => ({ data: 'testBinary' }));
const mockNode = {
id: 'test-node-id',
name: 'Microsoft OneDrive Test',
type: 'n8n-nodes-base.microsoftOneDrive',
typeVersion: 1.1,
position: [0, 0] as [number, number],
parameters: {},
};
beforeEach(() => {
mockExecuteFunctions = mock<IExecuteFunctions>();
microsoftOneDrive = new MicrosoftOneDrive();
mockExecuteFunctions.helpers = {
httpRequest,
prepareBinaryData,
returnJsonArray: jest.fn((data) => [data]),
constructExecutionMetaData: jest.fn((data) => data),
} as any;
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should call helpers.httpRequest when request to /drive/items/{fileId}/content fails', async () => {
const items = [{ json: { data: 'test' } }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
if (key === 'resource') return 'file';
if (key === 'operation') return 'download';
if (key === 'fileId') return 'fileID';
if (key === 'binaryPropertyName') return 'data';
});
const result = await microsoftOneDrive.execute.call(mockExecuteFunctions);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(2);
expect(httpRequest).toHaveBeenCalledTimes(1);
expect(httpRequest).toHaveBeenCalledWith({
encoding: 'arraybuffer',
json: false,
method: 'GET',
returnFullResponse: true,
url: 'https://test.com/file',
});
expect(prepareBinaryData).toHaveBeenCalledTimes(1);
expect(result).toEqual([
[{ binary: { data: { data: 'testBinary' } }, json: { data: 'test' } }],
]);
});
});
@@ -0,0 +1,165 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions, IBinaryData } from 'n8n-workflow';
import * as genericFunctions from '../../GenericFunctions';
import { MicrosoftOneDrive } from '../../MicrosoftOneDrive.node';
jest.mock('../../GenericFunctions', () => ({
...jest.requireActual('../../GenericFunctions'),
microsoftApiRequest: jest.fn(async function () {
return JSON.stringify({
id: 'uploadedFileId',
name: 'uploadedFile.txt',
});
}),
}));
describe('Test MicrosoftOneDrive, file > upload', () => {
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
let microsoftOneDrive: MicrosoftOneDrive;
const getBinaryDataBuffer = jest.fn(async () => Buffer.from('test content'));
const mockNode = {
id: 'test-node-id',
name: 'Microsoft OneDrive Test',
type: 'n8n-nodes-base.microsoftOneDrive',
typeVersion: 1.1,
position: [0, 0] as [number, number],
parameters: {},
};
beforeEach(() => {
mockExecuteFunctions = mock<IExecuteFunctions>();
microsoftOneDrive = new MicrosoftOneDrive();
mockExecuteFunctions.helpers = {
assertBinaryData: jest.fn(() => {
return {
data: 'base64data',
mimeType: 'text/plain',
fileName: 'binaryFileName.txt',
} as IBinaryData;
}),
getBinaryDataBuffer,
returnJsonArray: jest.fn((data) => [data]),
constructExecutionMetaData: jest.fn((data) => data),
} as any;
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
});
afterEach(() => {
jest.clearAllMocks();
});
it('should use filename from node parameters even when binary has a filename (version 1.1+)', async () => {
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
if (key === 'resource') return 'file';
if (key === 'operation') return 'upload';
if (key === 'parentId') return 'parentFolderId';
if (key === 'fileName') return 'customFileName.txt';
if (key === 'binaryData') return true;
if (key === 'binaryPropertyName') return 'data';
});
await microsoftOneDrive.execute.call(mockExecuteFunctions);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
'PUT',
'/drive/items/parentFolderId:/customFileName.txt:/content',
expect.any(Buffer),
{},
undefined,
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
{},
);
});
it('should use binary filename when node parameter filename is empty', async () => {
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
if (key === 'resource') return 'file';
if (key === 'operation') return 'upload';
if (key === 'parentId') return 'parentFolderId';
if (key === 'fileName') return '';
if (key === 'binaryData') return true;
if (key === 'binaryPropertyName') return 'data';
});
await microsoftOneDrive.execute.call(mockExecuteFunctions);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
'PUT',
'/drive/items/parentFolderId:/binaryFileName.txt:/content',
expect.any(Buffer),
{},
undefined,
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
{},
);
});
it('should properly encode special characters in filename from node parameters', async () => {
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
if (key === 'resource') return 'file';
if (key === 'operation') return 'upload';
if (key === 'parentId') return 'parentFolderId';
if (key === 'fileName') return 'file with spaces & special.txt';
if (key === 'binaryData') return true;
if (key === 'binaryPropertyName') return 'data';
});
await microsoftOneDrive.execute.call(mockExecuteFunctions);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
'PUT',
'/drive/items/parentFolderId:/file%20with%20spaces%20%26%20special.txt:/content',
expect.any(Buffer),
{},
undefined,
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
{},
);
});
it('should always use binary filename even when node parameter has a filename (version 1 - legacy bug)', async () => {
mockExecuteFunctions.getNode.mockReturnValue({
...mockNode,
typeVersion: 1,
});
const items = [{ json: { data: 'test' }, binary: { data: {} as IBinaryData } }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
if (key === 'resource') return 'file';
if (key === 'operation') return 'upload';
if (key === 'parentId') return 'parentFolderId';
if (key === 'fileName') return 'customFileName.txt';
if (key === 'binaryData') return true;
if (key === 'binaryPropertyName') return 'data';
});
await microsoftOneDrive.execute.call(mockExecuteFunctions);
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledTimes(1);
// In version 1, binary filename always overwrites the parameter filename
expect(genericFunctions.microsoftApiRequest).toHaveBeenCalledWith(
'PUT',
'/drive/items/parentFolderId:/binaryFileName.txt:/content',
expect.any(Buffer),
{},
undefined,
{ 'Content-Type': 'text/plain', 'Content-length': expect.any(Number) },
{},
);
});
});