blob: 02e1d89fcc1d22ea686aced1602292321fddd8d3 [file] [log] [blame]
'use strict';
require('../tools/js/v3-models.js');
const assert = require('assert');
global.FormData = require('form-data');
const TestServer = require('./resources/test-server.js');
const TemporaryFile = require('./resources/temporary-file.js').TemporaryFile;
const prepareServerTest = require('./resources/common-operations.js').prepareServerTest;
describe('/api/uploaded-file', function () {
prepareServerTest(this);
TemporaryFile.inject();
it('should return "InvalidArguments" when neither path nor sha256 query is set', () => {
return TestServer.remoteAPI().getJSON('/api/uploaded-file').then((content) => {
assert.strictEqual(content['status'], 'InvalidArguments');
return TestServer.remoteAPI().getJSON('/api/uploaded-file/');
}).then((content) => {
assert.strictEqual(content['status'], 'InvalidArguments');
});
});
it('should return 404 when there is no file with the specified ID', () => {
return TestServer.remoteAPI().getJSON('/api/uploaded-file/1').then((content) => {
assert(false, 'should never be reached');
}, (error) => {
assert.strictEqual(error, 404);
});
});
it('should return 404 when the specified ID is not a valid integer', () => {
return TestServer.remoteAPI().getJSON('/api/uploaded-file/foo').then((content) => {
assert(false, 'should never be reached');
}, (error) => {
assert.strictEqual(error, 404);
});
});
it('should return the file content matching the specified file ID', () => {
let uploadedFile;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
uploadedFile = response['uploadedFile'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${uploadedFile['id']}`, 'GET', null, null);
}).then((response) => {
assert.strictEqual(response.responseText, 'some content');
});
});
it('should return the file content with createdAt using POSIX timestamp in UTC', () => {
let uploadedFile;
const startTime = +Date.now();
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
uploadedFile = response['uploadedFile'];
console.assert(typeof(uploadedFile.createdAt) == 'number')
const createdAt = +new Date(uploadedFile.createdAt);
const endTime = +Date.now();
assert(startTime <= createdAt, 'createdAt should be after the time POST request was made');
assert(createdAt <= endTime, 'createdAt should be before the uploadedFile response had finished');
});
});
it('should return "NotFound" when the specified SHA256 is invalid', () => {
return TestServer.remoteAPI().getJSON('/api/uploaded-file/?sha256=abc').then((content) => {
assert.strictEqual(content['status'], 'NotFound');
});
});
it('should return "NotFound" when there is no file matching the specified SHA256 ', () => {
return TestServer.remoteAPI().getJSON('/api/uploaded-file/?sha256=5256ec18f11624025905d057d6befb03d77b243511ac5f77ed5e0221ce6d84b5').then((content) => {
assert.strictEqual(content['status'], 'NotFound');
});
});
it('should return the meta data of the file with the specified SHA256', () => {
let uploadedFile;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
uploadedFile = response['uploadedFile'];
return TestServer.remoteAPI().getJSON(`/api/uploaded-file/?sha256=${uploadedFile['sha256']}`);
}).then((response) => {
assert.deepStrictEqual(uploadedFile, response['uploadedFile']);
});
});
it('should return "NotFound" when the file matching the specified SHA256 had already been deleted', () => {
let uploadedFile;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
uploadedFile = response['uploadedFile'];
const db = TestServer.database();
return db.query(`UPDATE uploaded_files SET file_deleted_at = now() at time zone 'utc'`);
}).then(() => {
return TestServer.remoteAPI().getJSON(`/api/uploaded-file/?sha256=${uploadedFile['sha256']}`);
}).then((content) => {
assert.strictEqual(content['status'], 'NotFound');
});
});
it('should respond with ETag, Acccept-Ranges, Content-Disposition, Content-Length, and Last-Modified headers', () => {
let uploadedFile;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
uploadedFile = response['uploadedFile'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${uploadedFile['id']}`, 'GET', null, null);
}).then((response) => {
const headers = response.headers;
assert(Object.keys(headers).includes('etag'));
assert.strictEqual(headers['etag'], uploadedFile['sha256']);
assert(Object.keys(headers).includes('accept-ranges'));
assert.strictEqual(headers['accept-ranges'], 'bytes');
assert(Object.keys(headers).includes('content-disposition'));
assert.strictEqual(headers['content-disposition'], `attachment; filename*=utf-8''some.dat`);
assert(Object.keys(headers).includes('content-length'));
assert.strictEqual(headers['content-length'], uploadedFile['size']);
assert(Object.keys(headers).includes('last-modified'));
});
});
it('should respond with the same Last-Modified each time', () => {
let id;
let lastModified;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null);
}).then((response) => {
lastModified = response.headers['last-modified'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null);
}).then((response) => {
assert.strictEqual(response.headers['last-modified'], lastModified);
});
});
it('should respond with Content-Range when requested after X bytes', () => {
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=5-'}});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], 'bytes 5-11/12');
assert.strictEqual(response.responseText, 'content');
});
});
it('should respond with Content-Range when requested between X-Y bytes', () => {
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=4-9'}});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], 'bytes 4-9/12');
assert.strictEqual(response.responseText, ' conte');
});
});
it('should respond with Content-Range when requested for the last X bytes', () => {
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=-4'}});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], 'bytes 8-11/12');
assert.strictEqual(response.responseText, 'tent');
});
});
it('should respond with Content-Range for the whole content when the suffix length is larger than the content', () => {
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=-100'}});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], 'bytes 0-11/12');
assert.strictEqual(response.responseText, 'some content');
});
});
it('should return 416 when the starting byte is after the file size', () => {
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=12-'}})
.then(() => assert(false, 'should never be reached'), (error) => assert.strictEqual(error, 416));
});
});
it('should return 416 when the starting byte after the ending byte', () => {
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
const id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null, {headers: {'Range': 'bytes=2-1'}})
.then(() => assert(false, 'should never be reached'), (error) => assert.strictEqual(error, 416));
});
});
it('should respond with Content-Range when If-Range matches the last modified date', () => {
let id;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null);
}).then((response) => {
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.responseText, 'some content');
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
{headers: {'Range': 'bytes = 9-10', 'If-Range': response.headers['last-modified']}});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], 'bytes 9-10/12');
assert.strictEqual(response.responseText, 'en');
});
});
it('should respond with Content-Range when If-Range matches ETag', () => {
let id;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null);
}).then((response) => {
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.responseText, 'some content');
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
{headers: {'Range': 'bytes = 9-10', 'If-Range': response.headers['etag']}});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], 'bytes 9-10/12');
assert.strictEqual(response.responseText, 'en');
});
});
it('should return the full content when If-Range does not match the last modified date or ETag', () => {
let id;
return TemporaryFile.makeTemporaryFile('some.dat', 'some content').then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null);
}).then((response) => {
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.responseText, 'some content');
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
{'Range': 'bytes = 9-10', 'If-Range': 'foo'});
}).then((response) => {
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.responseText, 'some content');
});
});
it('should respond with Content-Range across 64KB streaming chunks', () => {
let id;
const fileSize = 256 * 1024;
const tokens = "0123456789abcdefghijklmnopqrstuvwxyz";
let buffer = Buffer.allocUnsafe(fileSize);
for (let i = 0; i < fileSize; i++)
buffer[i] = Math.floor(Math.random() * 256);
let startByte = 63 * 1024;
let endByte = 128 * 1024 - 1;
let responseBufferList = [];
const responseHandler = (response) => {
response.on('data', (chunk) => responseBufferList.push(chunk));
};
function verifyBuffer()
{
const responseBuffer = Buffer.concat(responseBufferList);
for (let i = 0; i < endByte - startByte + 1; i++) {
const actual = responseBuffer[i];
const expected = buffer[startByte + i];
assert.strictEqual(actual, expected, `The byte at index ${i} should be identical. Expected ${expected} but got ${actual}`);
}
}
return TemporaryFile.makeTemporaryFile('some.dat', buffer).then((stream) => {
return PrivilegedAPI.sendRequest('upload-file', {newFile: stream}, {useFormData: true});
}).then((response) => {
id = response['uploadedFile']['id'];
return TestServer.remoteAPI().sendHttpRequest(`/api/uploaded-file/${id}`, 'GET', null, null,
{headers: {'Range': `bytes = ${startByte}-${endByte}`}, responseHandler});
}).then((response) => {
const headers = response.headers;
assert.strictEqual(response.statusCode, 206);
assert.strictEqual(headers['content-range'], `bytes ${startByte}-${endByte}/${fileSize}`);
verifyBuffer();
});
});
});