fix(YouTube Node): Chunk file content before uploading (#15405)

This commit is contained in:
Michael Kret
2025-05-19 12:59:30 +03:00
committed by GitHub
parent 99361869a3
commit 3e855b4485
2 changed files with 122 additions and 4 deletions

View File

@@ -8,7 +8,7 @@ import type {
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionTypes, BINARY_ENCODING, NodeOperationError } from 'n8n-workflow';
import type { Readable } from 'stream';
import { Readable } from 'stream';
import { isoCountryCodes } from '@utils/ISOCountryCodes';
@@ -839,7 +839,7 @@ export class YouTube implements INodeType {
let mimeType: string;
let contentLength: number;
let fileContent: Buffer | Readable;
let fileContent: Readable;
if (binaryData.id) {
// Stream data in 256KB chunks, and upload the via the resumable upload api
@@ -848,8 +848,9 @@ export class YouTube implements INodeType {
contentLength = metadata.fileSize;
mimeType = metadata.mimeType ?? binaryData.mimeType;
} else {
fileContent = Buffer.from(binaryData.data, BINARY_ENCODING);
contentLength = fileContent.length;
const buffer = Buffer.from(binaryData.data, BINARY_ENCODING);
fileContent = Readable.from(buffer);
contentLength = buffer.length;
mimeType = binaryData.mimeType;
}

View File

@@ -0,0 +1,117 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import { Readable } from 'stream';
import * as genericFunctions from '../../GenericFunctions';
import { YouTube } from '../../YouTube.node';
jest.mock('../../GenericFunctions', () => {
const originalModule = jest.requireActual('../../GenericFunctions');
return {
...originalModule,
googleApiRequest: jest.fn(async function (method: string) {
if (method === 'POST') {
return {
headers: { location: 'https://www.youtube.com/watch?v=1234' },
body: { id: '1234' },
};
}
return {};
}),
};
});
const httpRequestMock = jest.fn(() => ({ id: '5678' }));
describe('Test YouTube, video => upload', () => {
let youTube: YouTube;
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
let fromSpy: jest.SpyInstance;
beforeEach(() => {
fromSpy = jest.spyOn(Readable, 'from');
youTube = new YouTube();
mockExecuteFunctions = mock<IExecuteFunctions>();
const buffer = Buffer.alloc(2 * 1024 * 1024, 'a');
mockExecuteFunctions.helpers = {
constructExecutionMetaData: jest.fn(() => []),
returnJsonArray: jest.fn(() => []),
assertBinaryData: jest.fn(() => ({ data: buffer.toString('base64'), mimeType: 'video/mp4' })),
httpRequest: httpRequestMock,
} as any;
});
afterEach(() => {
fromSpy.mockRestore();
jest.clearAllMocks();
});
it('should create readable from buffer', async () => {
const items = [{ json: { data: 'test' } }];
mockExecuteFunctions.getInputData.mockReturnValue(items);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
switch (key) {
case 'resource':
return 'video';
case 'operation':
return 'upload';
case 'title':
return 'test';
case 'categoryId':
return '11';
case 'options':
return {};
case 'binaryProperty':
return 'data';
default:
}
});
await youTube.execute.call(mockExecuteFunctions);
expect(genericFunctions.googleApiRequest).toHaveBeenCalledTimes(1);
expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith(
'POST',
'/upload/youtube/v3/videos',
{
recordingDetails: { recordingDate: undefined },
snippet: {
categoryId: '11',
defaultLanguage: undefined,
description: undefined,
tags: undefined,
title: 'test',
},
status: {
embeddable: undefined,
license: undefined,
privacyStatus: undefined,
publicStatsViewable: undefined,
publishAt: undefined,
selfDeclaredMadeForKids: undefined,
},
},
{
notifySubscribers: false,
part: 'snippet,status,recordingDetails',
uploadType: 'resumable',
},
undefined,
{
headers: { 'X-Upload-Content-Length': 2097152, 'X-Upload-Content-Type': 'video/mp4' },
json: true,
resolveWithFullResponse: true,
},
);
expect(httpRequestMock).toHaveBeenCalledWith({
body: expect.any(Object),
headers: { 'Content-Length': 2097152, 'Content-Range': 'bytes 0-2097151/2097152' },
method: 'PUT',
url: 'https://www.youtube.com/watch?v=1234',
});
expect(fromSpy).toHaveBeenCalledTimes(1);
});
});