mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-15 09:04:07 -08:00
78243edd18
Since we do not store which executions produced binary data, for pruning on S3 we need to query for binary data items for each execution in order to delete them. To minimize requests to S3, allow the user to skip pruning requests when setting TTL at bucket level.
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
import fs from 'node:fs/promises';
|
|
import { ObjectStoreManager } from '@/BinaryData/ObjectStore.manager';
|
|
import { ObjectStoreService } from '@/ObjectStore/ObjectStore.service.ee';
|
|
import { isStream } from '@/ObjectStore/utils';
|
|
import { mockInstance, toFileId, toStream } from './utils';
|
|
|
|
jest.mock('fs/promises');
|
|
|
|
const objectStoreService = mockInstance(ObjectStoreService);
|
|
const objectStoreManager = new ObjectStoreManager(objectStoreService);
|
|
|
|
const workflowId = 'ObogjVbqpNOQpiyV';
|
|
const executionId = '999';
|
|
const fileUuid = '71f6209b-5d48-41a2-a224-80d529d8bb32';
|
|
const fileId = toFileId(workflowId, executionId, fileUuid);
|
|
const prefix = `workflows/${workflowId}/executions/${executionId}/binary_data/`;
|
|
|
|
const otherWorkflowId = 'FHio8ftV6SrCAfPJ';
|
|
const otherExecutionId = '888';
|
|
const otherFileUuid = '71f6209b-5d48-41a2-a224-80d529d8bb33';
|
|
const otherFileId = toFileId(otherWorkflowId, otherExecutionId, otherFileUuid);
|
|
|
|
const mockBuffer = Buffer.from('Test data');
|
|
const mockStream = toStream(mockBuffer);
|
|
|
|
beforeAll(() => {
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
describe('store()', () => {
|
|
it('should store a buffer', async () => {
|
|
const metadata = { mimeType: 'text/plain' };
|
|
|
|
const result = await objectStoreManager.store(workflowId, executionId, mockBuffer, metadata);
|
|
|
|
expect(result.fileId.startsWith(prefix)).toBe(true);
|
|
expect(result.fileSize).toBe(mockBuffer.length);
|
|
});
|
|
});
|
|
|
|
describe('getPath()', () => {
|
|
it('should return a path', async () => {
|
|
const path = objectStoreManager.getPath(fileId);
|
|
|
|
expect(path).toBe(fileId);
|
|
});
|
|
});
|
|
|
|
describe('getAsBuffer()', () => {
|
|
it('should return a buffer', async () => {
|
|
// @ts-expect-error Overload signature seemingly causing the return type to be misinferred
|
|
objectStoreService.get.mockResolvedValue(mockBuffer);
|
|
|
|
const result = await objectStoreManager.getAsBuffer(fileId);
|
|
|
|
expect(Buffer.isBuffer(result)).toBe(true);
|
|
expect(objectStoreService.get).toHaveBeenCalledWith(fileId, { mode: 'buffer' });
|
|
});
|
|
});
|
|
|
|
describe('getAsStream()', () => {
|
|
it('should return a stream', async () => {
|
|
objectStoreService.get.mockResolvedValue(mockStream);
|
|
|
|
const stream = await objectStoreManager.getAsStream(fileId);
|
|
|
|
expect(isStream(stream)).toBe(true);
|
|
expect(objectStoreService.get).toHaveBeenCalledWith(fileId, { mode: 'stream' });
|
|
});
|
|
});
|
|
|
|
describe('getMetadata()', () => {
|
|
it('should return metadata', async () => {
|
|
const mimeType = 'text/plain';
|
|
const fileName = 'file.txt';
|
|
|
|
objectStoreService.getMetadata.mockResolvedValue({
|
|
'content-length': '1',
|
|
'content-type': mimeType,
|
|
'x-amz-meta-filename': fileName,
|
|
});
|
|
|
|
const metadata = await objectStoreManager.getMetadata(fileId);
|
|
|
|
expect(metadata).toEqual(expect.objectContaining({ fileSize: 1, mimeType, fileName }));
|
|
expect(objectStoreService.getMetadata).toHaveBeenCalledWith(fileId);
|
|
});
|
|
});
|
|
|
|
describe('copyByFileId()', () => {
|
|
it('should copy by file ID and return the file ID', async () => {
|
|
const targetFileId = await objectStoreManager.copyByFileId(workflowId, executionId, fileId);
|
|
|
|
expect(targetFileId.startsWith(prefix)).toBe(true);
|
|
expect(objectStoreService.get).toHaveBeenCalledWith(fileId, { mode: 'buffer' });
|
|
});
|
|
});
|
|
|
|
describe('copyByFilePath()', () => {
|
|
test('should copy by file path and return the file ID and size', async () => {
|
|
const sourceFilePath = 'path/to/file/in/filesystem';
|
|
const metadata = { mimeType: 'text/plain' };
|
|
|
|
fs.readFile = jest.fn().mockResolvedValue(mockBuffer);
|
|
|
|
const result = await objectStoreManager.copyByFilePath(
|
|
workflowId,
|
|
executionId,
|
|
sourceFilePath,
|
|
metadata,
|
|
);
|
|
|
|
expect(result.fileId.startsWith(prefix)).toBe(true);
|
|
expect(fs.readFile).toHaveBeenCalledWith(sourceFilePath);
|
|
expect(result.fileSize).toBe(mockBuffer.length);
|
|
});
|
|
});
|
|
|
|
describe('rename()', () => {
|
|
it('should rename a file', async () => {
|
|
const promise = objectStoreManager.rename(fileId, otherFileId);
|
|
|
|
await expect(promise).resolves.not.toThrow();
|
|
|
|
expect(objectStoreService.get).toHaveBeenCalledWith(fileId, { mode: 'buffer' });
|
|
expect(objectStoreService.getMetadata).toHaveBeenCalledWith(fileId);
|
|
expect(objectStoreService.deleteOne).toHaveBeenCalledWith(fileId);
|
|
});
|
|
});
|