@skalenetwork/filestorage.js
Version:
SKALE Filestorage client tools
517 lines (463 loc) • 24.3 kB
JavaScript
/**
* @license
* SKALE Filestorage-js
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* @file index.test.js
* @copyright SKALE Labs 2019-Present
*/
const FilestorageClient = require('../src/index');
const FilestorageContract = require('../src/FilestorageContract');
const helper = require('../src/common/helper');
const testConstants = require('./utils/constants');
const testHelper = require('./utils/helper');
const constants = require('../src/common/constants');
const path = require('path');
const Web3 = require('web3');
const randomstring = require('randomstring');
const fs = require('fs');
require('dotenv').config();
const chai = require('chai');
const assert = chai.assert;
const expect = chai.expect;
const errorMessages = testConstants.errorMessages;
const fileStatus = testConstants.fileStatus;
chai.should();
chai.use(require('chai-as-promised'));
describe('Test FilestorageClient', function () {
let privateKey = process.env.PRIVATEKEY;
let foreignPrivateKey = process.env.FOREIGN_PRIVATEKEY;
let emptyAddress = testConstants.EMPTY_ADDRESS;
let bigFilePath = testConstants.TEST_FILE_PATH;
let filestorage;
let address;
let foreignAddress;
before(async function () {
// eslint-disable-next-line
filestorage = new FilestorageClient(process.env.SKALE_ENDPOINT, true);
address = testHelper.getAddress(privateKey);
foreignAddress = testHelper.getAddress(foreignPrivateKey);
await testHelper.getFunds(address);
await testHelper.getFunds(foreignAddress);
await testHelper.reserveTestSpace(filestorage.contract.contract, address, testConstants.RESERVED_SPACE);
await testHelper.reserveTestSpace(filestorage.contract.contract, foreignAddress, testConstants.RESERVED_SPACE);
});
describe('Test constructor', function () {
it('should initialize with web3', function () {
const web3Provider = new Web3.providers.HttpProvider(process.env.SKALE_ENDPOINT);
let web3 = new Web3(web3Provider);
let filestorageClient = new FilestorageClient(web3);
assert.instanceOf(filestorageClient, FilestorageClient);
assert.instanceOf(filestorageClient.web3, Web3);
assert.instanceOf(filestorageClient.contract, FilestorageContract);
});
it('should initialize with http endpoint', function () {
let filestorageClient = new FilestorageClient(process.env.SKALE_ENDPOINT);
assert.instanceOf(filestorageClient, FilestorageClient);
assert.instanceOf(filestorageClient.web3, Web3);
assert.instanceOf(filestorageClient.contract, FilestorageContract);
});
it('should initialize with ws endpoint', async function () {
let filestorageClient = new FilestorageClient('ws://127.0.0.1:1234');
assert.instanceOf(filestorageClient, FilestorageClient);
assert.instanceOf(filestorageClient.web3, Web3);
assert.instanceOf(filestorageClient.contract, FilestorageContract);
});
it('should initialize with enabled logs', function () {
let filestorageClient = new FilestorageClient(process.env.SKALE_ENDPOINT, true);
assert.isTrue(filestorageClient.enableLogs);
});
});
// TODO: test big files uploading
describe('Test uploading', function () {
let fileName;
let dirPath;
let data;
describe('Positive tests', function () {
beforeEach(function () {
fileName = 'test_' + randomstring.generate();
data = Buffer.from(fileName);
dirPath = helper.rmBytesSymbol(address) + '/';
});
it('Uploading file with private key', async function () {
let filepath = await filestorage.uploadFile(address, fileName, data, privateKey);
assert.isTrue(filepath === helper.rmBytesSymbol(address) + '/' + fileName, 'Invalid storagePath');
});
it('Uploading file with private key and address beginning with 0x', async function () {
let filePath = await filestorage.uploadFile(helper.addBytesSymbol(address), fileName, data, privateKey);
assert.isTrue(filePath === helper.rmBytesSymbol(address) + '/' + fileName, 'Invalid storagePath');
});
it('Uploading file with private key without 0x', async function () {
let filePath = await filestorage.uploadFile(address, fileName, data, helper.rmBytesSymbol(privateKey));
assert.isTrue(filePath === helper.rmBytesSymbol(address) + '/' + fileName, 'Invalid storagePath');
});
it('Uploading file with private key and address beginning with 0x', async function () {
let filePath = await filestorage.uploadFile(helper.addBytesSymbol(address), fileName, data, privateKey);
assert.isTrue(filePath === helper.rmBytesSymbol(address) + '/' + fileName, 'Invalid storagePath');
});
it('Uploading file with private key without 0x and address beginning with 0x', async function () {
let filePath = await filestorage.uploadFile(address, fileName, data, helper.rmBytesSymbol(privateKey));
assert.isTrue(filePath === helper.rmBytesSymbol(address) + '/' + fileName, 'Invalid storagePath');
});
it('Uploading file in directory', async function () {
let directoryName = randomstring.generate();
await filestorage.createDirectory(address, directoryName, privateKey);
let relativePath = path.posix.join(directoryName, fileName);
dirPath = path.posix.join(dirPath, directoryName);
let filePath = await filestorage.uploadFile(address, relativePath, data, privateKey);
assert.isTrue(filePath === helper.rmBytesSymbol(address) + '/' + relativePath, 'Invalid storagePath');
});
afterEach('Checking file\'s existence', async function () {
let fileList = await filestorage.listDirectory(dirPath);
let isFind = fileList.find(obj => {
return obj.name === fileName;
});
assert.isObject(isFind, 'File doesn\'t exist');
});
});
describe('Negative tests', function () {
let data;
let fileName;
beforeEach(function () {
data = Buffer.from(randomstring.generate());
fileName = randomstring.generate();
});
it('Uploading file with foreign privateKey', async function () {
await filestorage.uploadFile(address, fileName, data, foreignPrivateKey)
.should
.eventually
.rejectedWith(errorMessages.INVALID_KEYPAIR);
});
it('Uploading file with size > 100mb', async function () {
let fileData = fs.readFileSync(bigFilePath);
await filestorage.uploadFile(address, fileName, fileData, privateKey)
.should
.eventually
.rejectedWith(errorMessages.INCORRECT_FILESIZE);
});
it('Uploading file with name > 255 chars', async function () {
let fileName = randomstring.generate(256);
await filestorage.uploadFile(address, fileName, data, privateKey)
.should
.eventually
.rejectedWith(errorMessages.INCORRECT_FILENAME);
});
it('Uploading file with existing name', async function () {
await filestorage.uploadFile(address, fileName, data, privateKey);
await filestorage.uploadFile(address, fileName, data, privateKey)
.should
.eventually
.rejectedWith(errorMessages.FILE_ALREADY_EXISTS);
});
});
});
describe('Test downloading', function () {
describe('Positive tests', function () {
let fileName;
let data;
beforeEach(function () {
fileName = 'test_' + randomstring.generate();
data = Buffer.from(randomstring.generate());
});
it('Download own file', async function () {
let storagePath = await filestorage.uploadFile(address, fileName, data, privateKey);
let buffer = await filestorage.downloadToBuffer(storagePath);
expect(buffer).to.be.instanceOf(Buffer);
assert.deepEqual(buffer, data, 'File downloaded incorrectly');
});
it('Download foreign file', async function () {
let storagePath = await filestorage.uploadFile(foreignAddress, fileName, data, foreignPrivateKey);
let buffer = await filestorage.downloadToBuffer(storagePath);
expect(buffer).to.be.instanceOf(Buffer);
assert.deepEqual(buffer, data, 'File downloaded incorrectly');
});
it('Download file from directory', async function () {
let directoryName = randomstring.generate();
await filestorage.createDirectory(address, directoryName, privateKey);
fileName = path.posix.join(directoryName, fileName);
let storagePath = await filestorage.uploadFile(address, fileName, data, privateKey);
let buffer = await filestorage.downloadToBuffer(storagePath);
expect(buffer).to.be.instanceOf(Buffer);
assert.deepEqual(buffer, data, 'File downloaded incorrectly');
});
});
describe('Negative tests', function () {
it('Download nonexistent file', async function () {
let storagePath = randomstring.generate();
await filestorage.downloadToBuffer(storagePath)
.should
.eventually
.rejectedWith(errorMessages.INVALID_STORAGEPATH);
});
it('Download using downloadToFile', async function () {
let storagePath = randomstring.generate();
await filestorage.downloadToFile(storagePath)
.should
.eventually
.rejectedWith(errorMessages.ONLY_BROWSER_METHOD);
});
// TODO: Download unfinished file
});
});
describe('Test deleting', function () {
describe('Positive tests', function () {
let fileName;
beforeEach(async function () {
fileName = 'delete_' + randomstring.generate();
let data = Buffer.from(randomstring.generate());
await filestorage.uploadFile(address, fileName, data, privateKey);
});
afterEach(async function () {
let fileList = await filestorage.listDirectory(helper.rmBytesSymbol(address) + '/');
let isFind = fileList.find(obj => {
return obj.name === fileName;
});
assert.isUndefined(isFind, 'File not deleted');
});
it('should delete existing own file', async function () {
await filestorage.deleteFile(address, fileName, privateKey);
});
it('should delete file from directory', async function () {
let directoryName = randomstring.generate();
await filestorage.createDirectory(address, directoryName, privateKey);
fileName = path.posix.join(directoryName, fileName);
let data = Buffer.from(randomstring.generate());
await filestorage.uploadFile(address, fileName, data, privateKey);
await filestorage.deleteFile(address, fileName, privateKey);
});
// TODO: Delete unfinished file
});
describe('Negative tests', function () {
it('should delete nonexistent own file', async function () {
let fileName = 'delete_' + randomstring.generate();
await filestorage.deleteFile(address, fileName, privateKey)
.should
.eventually
.rejectedWith(errorMessages.INVALID_PATH);
});
it('should delete foreign file', async function () {
let fileName = 'delete_' + randomstring.generate();
let data = Buffer.from(randomstring.generate());
await filestorage.uploadFile(foreignAddress, fileName, data, foreignPrivateKey);
await filestorage.deleteFile(address, fileName, privateKey)
.should
.eventually
.rejectedWith(errorMessages.INVALID_PATH);
});
});
});
describe('Test createDirectory', function () {
describe('Positive tests', function () {
it('should create directory', async function () {
let directoryName = randomstring.generate();
await filestorage.createDirectory(address, directoryName, privateKey);
let contents = await filestorage.listDirectory(helper.rmBytesSymbol(address) + '/');
assert.isNotEmpty(contents);
assert.isObject(contents.find(obj => {
return obj.name === directoryName;
}));
});
});
});
describe('Test listDirectory', function () {
describe('Positive tests', function () {
let fileName;
let directoryName;
function isValidStoragePath(storagePath) {
let re = new RegExp('([0-9]|[a-f]|[A-F]){40}\\/.+');
return re.test(storagePath);
}
before(async function () {
directoryName = randomstring.generate();
fileName = randomstring.generate();
let data = Buffer.from(fileName);
await filestorage.createDirectory(address, directoryName, privateKey);
await filestorage.uploadFile(address, fileName, data, privateKey);
let filePath = path.posix.join(directoryName, fileName);
await filestorage.uploadFile(address, filePath, data, privateKey);
});
it('should list root directory', async function () {
let contentList = await filestorage.listDirectory(helper.rmBytesSymbol(address) + '/');
assert.isArray(contentList);
assert.isNotEmpty(contentList);
assert.isObject(contentList.find(obj => {
return obj.name === directoryName;
}), 'Directory is absent');
assert.isObject(contentList.find(obj => {
return obj.name === fileName;
}), 'File is absent');
});
it('should list root directory without /', async function () {
let contentList = await filestorage.listDirectory(helper.rmBytesSymbol(address));
assert.isArray(contentList);
assert.isNotEmpty(contentList);
assert.isObject(contentList.find(obj => {
return obj.name === directoryName;
}), 'Directory is absent');
assert.isObject(contentList.find(obj => {
return obj.name === fileName;
}), 'File is absent');
});
it('should list nested directory', async function () {
let directoryPath = path.posix.join(helper.rmBytesSymbol(address), directoryName);
let contentList = await filestorage.listDirectory(directoryPath);
assert.isArray(contentList);
assert.isNotEmpty(contentList);
assert.isObject(contentList.find(obj => {
return obj.name === fileName;
}));
});
it('should return file info in specific format', async function () {
let fileName = randomstring.generate();
let data = Buffer.from(randomstring.generate({
length: 2 * constants.CHUNK_LENGTH,
charset: 'hex'
}), 'hex');
await filestorage.uploadFile(address, fileName, data, privateKey);
let content = await filestorage.listDirectory(helper.rmBytesSymbol(address) + '/');
let fileInfo = content.find(obj => {
return obj.name === fileName;
});
assert.isObject(fileInfo);
assert.lengthOf(Object.keys(fileInfo), 6, 'Incorrect length of fileInfo');
assert.isTrue(fileInfo.name === fileName, 'Incorrect fileName');
assert.isTrue(isValidStoragePath(fileInfo.storagePath), 'Invalid storagePath');
assert.isTrue(fileInfo.storagePath === path.posix.join(helper.rmBytesSymbol(address), fileName),
'Invalid storagePath');
assert.isTrue(fileInfo.isFile, 'Incorrect isFile');
assert.isTrue(fileInfo.size === constants.CHUNK_LENGTH, 'Incorrect fileSize');
assert.isTrue(fileInfo.status === fileStatus.STATUS_COMPLETED, 'Finished file: incorrect status');
assert.isTrue(fileInfo.uploadingProgress === 100, 'Finished file: incorrect chunks');
});
it('should return dir info in specific format', async function () {
let dirName = randomstring.generate();
let dirPath = path.posix.join(helper.rmBytesSymbol(address), dirName);
await filestorage.createDirectory(address, dirName, privateKey);
let content = await filestorage.listDirectory(helper.rmBytesSymbol(address) + '/');
let dirInfo = content.find(obj => {
return obj.name === dirName;
});
assert.isObject(dirInfo);
assert.lengthOf(Object.keys(dirInfo), 3, 'Incorrect length of dirInfo');
assert.equal(dirInfo.name, dirName, 'Incorrect dirName');
assert.isTrue(isValidStoragePath(dirInfo.storagePath), 'Invalid storagePath of dir');
assert.equal(dirInfo.storagePath, dirPath, 'Incorrect dir storagePath');
assert.isFalse(dirInfo.isFile, 'Incorrect isFile');
});
it('should return empty array whether user has no files', async function () {
let files = await filestorage.listDirectory(helper.rmBytesSymbol(emptyAddress) + '/');
assert.isArray(files, 'should return array');
assert.isEmpty(files, 'should contain files');
});
it('should fail listing nonexistent dir', async function () {
await filestorage.listDirectory(path.posix.join(helper.rmBytesSymbol(emptyAddress), 'void'))
.should.eventually.rejectedWith(errorMessages.INVALID_PATH);
await filestorage.listDirectory('')
.should.eventually.rejectedWith(errorMessages.INVALID_STORAGEPATH);
await filestorage.listDirectory(' ')
.should.eventually.rejectedWith(errorMessages.INVALID_STORAGEPATH);
await filestorage.listDirectory(helper.addBytesSymbol(address))
.should.eventually.rejectedWith(errorMessages.INVALID_STORAGEPATH);
});
});
});
describe('test deleteDirectory', function () {
describe('Positive tests', function () {
let directoryName;
beforeEach(async function () {
directoryName = randomstring.generate();
await filestorage.createDirectory(address, directoryName, privateKey);
});
it('should delete empty directory', async function () {
await filestorage.deleteDirectory(address, directoryName, privateKey);
let contents = await filestorage.listDirectory(helper.rmBytesSymbol(address) + '/');
assert.isNotEmpty(contents);
assert.isTrue(contents.indexOf(directoryName) === -1);
});
});
});
describe('test reserveSpace', function () {
describe('Positive tests', function () {
it('should reserve space for account', async function () {
let owner = testHelper.getAddress(process.env.SCHAIN_OWNER_PK);
await filestorage.reserveSpace(
owner,
testConstants.SPACE_TEST_ADDRESS,
100,
process.env.SCHAIN_OWNER_PK
);
});
});
});
describe('test grantAllocatorRole', function () {
describe('Positive tests', function () {
it('should grant allocator role for account', async function () {
let owner = testHelper.getAddress(process.env.SCHAIN_OWNER_PK);
let account = await filestorage.web3.eth.accounts.create();
await filestorage.grantAllocatorRole(
owner,
account.address,
process.env.SCHAIN_OWNER_PK);
let isGranted = await filestorage.contract.contract.methods.hasRole(
await filestorage.contract.contract.methods.ALLOCATOR_ROLE().call(),
account.address
).call();
assert(isGranted === true);
});
});
});
describe('getters', function () {
it('should return total storage space', async function () {
let space = await filestorage.getTotalSpace();
assert.isNumber(space);
assert(space > 0);
});
it('should return total reserved storage space', async function () {
let space = await filestorage.getTotalReservedSpace();
assert.isNumber(space);
assert(space > 0);
});
it('should return reserved storage space for account', async function () {
let owner = testHelper.getAddress(process.env.SCHAIN_OWNER_PK);
await filestorage.reserveSpace(
owner,
testConstants.SPACE_TEST_ADDRESS,
200,
process.env.SCHAIN_OWNER_PK
);
let space = await filestorage.getReservedSpace(testConstants.SPACE_TEST_ADDRESS);
assert.isNumber(space);
assert(space === 200);
await filestorage.reserveSpace(
owner,
testConstants.SPACE_TEST_ADDRESS,
300,
process.env.SCHAIN_OWNER_PK
);
space = await filestorage.getReservedSpace(testConstants.SPACE_TEST_ADDRESS);
assert(space === 300);
});
it('should return total occupied storage space', async function () {
let initSpace = await filestorage.getOccupiedSpace(address);
assert.isNumber(initSpace);
let fileName = 'test_' + randomstring.generate();
let data = Buffer.from(fileName);
await filestorage.uploadFile(address, fileName, data, privateKey);
let afterSpace = await filestorage.getOccupiedSpace(address);
assert(afterSpace - initSpace === 4096);
});
});
});