fix(GoogleDrive Node): Fix google service accounts uploading to shared drives (#18952)

This commit is contained in:
Marcus
2025-09-04 21:23:41 +02:00
committed by GitHub
parent f8a732bfb3
commit 63672ad797
4 changed files with 89 additions and 38 deletions

View File

@@ -61,9 +61,14 @@ describe('test GoogleDriveV2: file createFromText', () => {
'POST',
'/upload/drive/v3/files',
expect.anything(), // Buffer of content goes here
{ uploadType: 'media' },
{ uploadType: 'multipart', supportsAllDrives: true },
undefined,
{ headers: { 'Content-Length': 12, 'Content-Type': 'text/plain' } },
{
headers: {
'Content-Length': 503,
'Content-Type': expect.stringMatching(/^multipart\/related; boundary=(\\S)*/),
},
},
);
expect(transport.googleApiRequest).toHaveBeenCalledWith(
'PATCH',

View File

@@ -5,6 +5,11 @@ import * as utils from '../../../../v2/helpers/utils';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction, createTestStream, driveNode } from '../helpers';
const fileContent = Buffer.from('Hello Drive!');
const originalFilename = 'original.txt';
const contentLength = 123;
const mimeType = 'text/plain';
jest.mock('../../../../v2/transport', () => {
const originalModule = jest.requireActual('../../../../v2/transport');
return {
@@ -26,10 +31,10 @@ jest.mock('../../../../v2/helpers/utils', () => {
...originalModule,
getItemBinaryData: jest.fn(async function () {
return {
contentLength: '123',
fileContent: Buffer.from('Hello Drive!'),
originalFilename: 'original.txt',
mimeType: 'text/plain',
contentLength,
fileContent,
originalFilename,
mimeType,
};
}),
};
@@ -41,11 +46,13 @@ describe('test GoogleDriveV2: file upload', () => {
});
it('should upload buffers', async () => {
const name = 'newFile.txt';
const parent = 'folderIDxxxxxx';
const nodeParameters = {
name: 'newFile.txt',
name,
folderId: {
__rl: true,
value: 'folderIDxxxxxx',
value: parent,
mode: 'list',
cachedResultName: 'testFolder 3',
cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
@@ -65,16 +72,21 @@ describe('test GoogleDriveV2: file upload', () => {
'POST',
'/upload/drive/v3/files',
expect.any(Buffer),
{ uploadType: 'media' },
{ uploadType: 'multipart', supportsAllDrives: true },
undefined,
{ headers: { 'Content-Length': '123', 'Content-Type': 'text/plain' } },
{
headers: {
'Content-Length': 498,
'Content-Type': expect.stringMatching(/^multipart\/related; boundary=(\\S)*/),
},
},
);
expect(transport.googleApiRequest).toHaveBeenCalledWith(
'PATCH',
'/drive/v3/files/undefined',
{ mimeType: 'text/plain', name: 'newFile.txt', originalFilename: 'original.txt' },
{ mimeType, name, originalFilename },
{
addParents: 'folderIDxxxxxx',
addParents: parent,
supportsAllDrives: true,
corpora: 'allDrives',
includeItemsFromAllDrives: true,
@@ -87,11 +99,15 @@ describe('test GoogleDriveV2: file upload', () => {
});
it('should stream large files in 2MB chunks', async () => {
const name = 'newFile.jpg';
const parent = 'folderIDxxxxxx';
const originalFilename = 'test.jpg';
const mimeType = 'image/jpg';
const nodeParameters = {
name: 'newFile.jpg',
name,
folderId: {
__rl: true,
value: 'folderIDxxxxxx',
value: parent,
mode: 'list',
cachedResultName: 'testFolder 3',
cachedResultUrl: 'https://drive.google.com/drive/folders/folderIDxxxxxx',
@@ -106,8 +122,8 @@ describe('test GoogleDriveV2: file upload', () => {
const fileSize = 7 * 1024 * 1024; // 7MB
jest.mocked(utils.getItemBinaryData).mockResolvedValue({
mimeType: 'image/jpg',
originalFilename: 'test.jpg',
mimeType,
originalFilename,
contentLength: fileSize,
fileContent: createTestStream(fileSize),
});
@@ -123,17 +139,17 @@ describe('test GoogleDriveV2: file upload', () => {
expect(transport.googleApiRequest).toHaveBeenCalledWith(
'POST',
'/upload/drive/v3/files',
undefined,
{ uploadType: 'resumable' },
{ name, parents: [parent] },
{ uploadType: 'resumable', supportsAllDrives: true },
undefined,
{ returnFullResponse: true, headers: { 'X-Upload-Content-Type': 'image/jpg' } },
);
expect(transport.googleApiRequest).toHaveBeenCalledWith(
'PATCH',
'/drive/v3/files/undefined',
{ mimeType: 'image/jpg', name: 'newFile.jpg', originalFilename: 'test.jpg' },
{ mimeType, name, originalFilename },
{
addParents: 'folderIDxxxxxx',
addParents: parent,
supportsAllDrives: true,
corpora: 'allDrives',
includeItemsFromAllDrives: true,

View File

@@ -12,6 +12,8 @@ import { setFileProperties, setParentFolder, setUpdateCommonParams } from '../..
import { googleApiRequest } from '../../transport';
import { driveRLC, folderRLC, updateCommonOptions } from '../common.descriptions';
import FormData from 'form-data';
const properties: INodeProperties[] = [
{
displayName: 'File Content',
@@ -88,14 +90,13 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
extractValue: true,
}) as string;
const bodyParameters = setFileProperties(
{
name,
parents: [setParentFolder(folderId, driveId)],
mimeType,
},
options,
);
const metadata = {
name,
parents: [setParentFolder(folderId, driveId)],
mimeType,
};
const bodyParameters = setFileProperties(metadata, options);
const qs = setUpdateCommonParams(
{
@@ -146,19 +147,29 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
const content = Buffer.from(this.getNodeParameter('content', i, '') as string, 'utf8');
const contentLength = content.byteLength;
const multiPartBody = new FormData();
multiPartBody.append('metadata', JSON.stringify(metadata), {
contentType: 'application/json',
});
multiPartBody.append('data', content, {
contentType: mimeType,
knownLength: contentLength,
});
const uploadData = await googleApiRequest.call(
this,
'POST',
'/upload/drive/v3/files',
content,
multiPartBody.getBuffer(),
{
uploadType: 'media',
uploadType: 'multipart',
supportsAllDrives: true,
},
undefined,
{
headers: {
'Content-Type': mimeType,
'Content-Length': contentLength,
'Content-Type': `multipart/related; boundary=${multiPartBody.getBoundary()}`,
'Content-Length': multiPartBody.getLengthSync(),
},
},
);

View File

@@ -1,3 +1,5 @@
import FormData from 'form-data';
import type {
IDataObject,
IExecuteFunctions,
@@ -97,20 +99,34 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
}) as string;
let uploadId;
const metadata = {
name,
parents: [setParentFolder(folderId, driveId)],
};
if (Buffer.isBuffer(fileContent)) {
const multiPartBody = new FormData();
multiPartBody.append('metadata', JSON.stringify(metadata), {
contentType: 'application/json',
});
multiPartBody.append('data', fileContent, {
contentType: mimeType,
knownLength: contentLength,
});
const response = await googleApiRequest.call(
this,
'POST',
'/upload/drive/v3/files',
fileContent,
multiPartBody.getBuffer(),
{
uploadType: 'media',
uploadType: 'multipart',
supportsAllDrives: true,
},
undefined,
{
headers: {
'Content-Type': mimeType,
'Content-Length': contentLength,
'Content-Type': `multipart/related; boundary=${multiPartBody.getBoundary()}`,
'Content-Length': multiPartBody.getLengthSync(),
},
},
);
@@ -121,8 +137,11 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
this,
'POST',
'/upload/drive/v3/files',
undefined,
{ uploadType: 'resumable' },
metadata,
{
uploadType: 'resumable',
supportsAllDrives: true,
},
undefined,
{
returnFullResponse: true,