n8cn / packages /cli /src /controllers /__tests__ /binary-data.controller.test.ts
gallyga's picture
Add n8n Chinese version
aec3094
import type { BinaryDataQueryDto, BinaryDataSignedQueryDto } from '@n8n/api-types';
import type { Request, Response } from 'express';
import { mock } from 'jest-mock-extended';
import { JsonWebTokenError } from 'jsonwebtoken';
import type { BinaryDataService } from 'n8n-core';
import { FileNotFoundError } from 'n8n-core';
import type { Readable } from 'node:stream';
import { BinaryDataController } from '../binary-data.controller';
describe('BinaryDataController', () => {
const request = mock<Request>();
const response = mock<Response>();
const binaryDataService = mock<BinaryDataService>();
const controller = new BinaryDataController(binaryDataService);
beforeEach(() => {
jest.resetAllMocks();
response.status.mockReturnThis();
});
describe('get', () => {
it('should return 400 if binary data mode is missing', async () => {
const query = { id: '123', action: 'view' } as BinaryDataQueryDto;
await controller.get(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.end).toHaveBeenCalledWith('Malformed binary data ID');
});
it('should return 400 if binary data mode is invalid', async () => {
const query = { id: 'invalidMode:123', action: 'view' } as BinaryDataQueryDto;
await controller.get(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.end).toHaveBeenCalledWith('Invalid binary data mode');
});
it('should return 404 if file is not found', async () => {
const query = {
id: 'filesystem:123',
action: 'view',
mimeType: 'image/jpeg',
} as BinaryDataQueryDto;
binaryDataService.getAsStream.mockRejectedValue(new FileNotFoundError('File not found'));
await controller.get(request, response, query);
expect(response.status).toHaveBeenCalledWith(404);
expect(response.end).toHaveBeenCalled();
});
it('should set Content-Type and Content-Length from query if provided', async () => {
const query = {
id: 'filesystem:123',
action: 'view',
fileName: 'test.txt',
mimeType: 'text/plain',
} as BinaryDataQueryDto;
binaryDataService.getAsStream.mockResolvedValue(mock());
await controller.get(request, response, query);
expect(binaryDataService.getMetadata).toHaveBeenCalledWith(query.id);
expect(response.setHeader).toHaveBeenCalledWith('Content-Type', 'text/plain');
expect(response.setHeader).not.toHaveBeenCalledWith('Content-Length');
expect(response.setHeader).not.toHaveBeenCalledWith('Content-Disposition');
});
it('should set Content-Type and Content-Length from metadata if not provided', async () => {
const query = { id: 'filesystem:123', action: 'view' } as BinaryDataQueryDto;
binaryDataService.getMetadata.mockResolvedValue({
fileName: 'test.txt',
mimeType: 'text/plain',
fileSize: 100,
});
binaryDataService.getAsStream.mockResolvedValue(mock());
await controller.get(request, response, query);
expect(binaryDataService.getMetadata).toHaveBeenCalledWith('filesystem:123');
expect(response.setHeader).toHaveBeenCalledWith('Content-Type', 'text/plain');
expect(response.setHeader).toHaveBeenCalledWith('Content-Length', 100);
expect(response.setHeader).not.toHaveBeenCalledWith('Content-Disposition');
});
it('should set Content-Disposition for download action', async () => {
const query = {
id: 'filesystem:123',
action: 'download',
fileName: 'test.txt',
} as BinaryDataQueryDto;
binaryDataService.getAsStream.mockResolvedValue(mock());
await controller.get(request, response, query);
expect(response.setHeader).toHaveBeenCalledWith(
'Content-Disposition',
'attachment; filename="test.txt"',
);
});
it('should not allow viewing of html files', async () => {
const query = {
id: 'filesystem:123',
action: 'view',
fileName: 'test.html',
mimeType: 'text/html',
} as BinaryDataQueryDto;
binaryDataService.getAsStream.mockResolvedValue(mock());
await controller.get(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.setHeader).not.toHaveBeenCalled();
});
it('should allow viewing of jpeg files, and handle mime-type casing', async () => {
const query = {
id: 'filesystem:123',
action: 'view',
fileName: 'test.jpg',
mimeType: 'image/Jpeg',
} as BinaryDataQueryDto;
binaryDataService.getAsStream.mockResolvedValue(mock());
await controller.get(request, response, query);
expect(response.status).not.toHaveBeenCalledWith(400);
expect(response.setHeader).toHaveBeenCalledWith('Content-Type', query.mimeType);
});
it('should return the file stream on success', async () => {
const query = {
id: 'filesystem:123',
action: 'view',
mimeType: 'image/jpeg',
} as BinaryDataQueryDto;
const stream = mock<Readable>();
binaryDataService.getAsStream.mockResolvedValue(stream);
const result = await controller.get(request, response, query);
expect(result).toBe(stream);
expect(binaryDataService.getAsStream).toHaveBeenCalledWith('filesystem:123');
});
describe('with malicious binary data IDs', () => {
it.each([
['filesystem:'],
['filesystem-v2:'],
['filesystem:/'],
['filesystem-v2:/'],
['filesystem://'],
['filesystem-v2://'],
])('should return 400 for ID "%s"', async (maliciousId) => {
const query = { id: maliciousId, action: 'download' } as BinaryDataQueryDto;
await controller.get(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.end).toHaveBeenCalledWith('Malformed binary data ID');
});
});
});
describe('getSigned', () => {
const query = { token: '12344' } as BinaryDataSignedQueryDto;
it('should return 400 if binary data mode is missing', async () => {
binaryDataService.validateSignedToken.mockReturnValueOnce('123');
await controller.getSigned(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.end).toHaveBeenCalledWith('Malformed binary data ID');
});
it('should return 400 if binary data mode is invalid', async () => {
binaryDataService.validateSignedToken.mockReturnValueOnce('invalid:123');
await controller.getSigned(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.end).toHaveBeenCalledWith('Invalid binary data mode');
});
it('should return 400 if token is invalid', async () => {
binaryDataService.validateSignedToken.mockImplementation(() => {
throw new JsonWebTokenError('Invalid token');
});
await controller.getSigned(request, response, query);
expect(response.status).toHaveBeenCalledWith(400);
expect(response.end).toHaveBeenCalledWith('Invalid token');
});
it('should return 404 if file is not found', async () => {
binaryDataService.validateSignedToken.mockReturnValueOnce('filesystem:123');
binaryDataService.getAsStream.mockRejectedValue(new FileNotFoundError('File not found'));
await controller.getSigned(request, response, query);
expect(response.status).toHaveBeenCalledWith(404);
expect(response.end).toHaveBeenCalled();
});
it('should return the file stream on a valid signed token', async () => {
binaryDataService.validateSignedToken.mockReturnValueOnce('filesystem:123');
const stream = mock<Readable>();
binaryDataService.getAsStream.mockResolvedValue(stream);
const result = await controller.getSigned(request, response, query);
expect(result).toBe(stream);
expect(binaryDataService.getAsStream).toHaveBeenCalledWith('filesystem:123');
});
});
});