403 lines
16 KiB
JavaScript
403 lines
16 KiB
JavaScript
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2024
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
|
|
const {jest, describe, test, expect, beforeAll, afterAll} = require('@jest/globals');
|
|
jest.mock('fs/promises', () => ({
|
|
...jest.requireActual('fs/promises'),
|
|
cp: jest.fn().mockImplementation((from, to) => fs.writeFileSync(to, testFileData3))
|
|
}));
|
|
const mockNeedServeStatic = jest.fn().mockReturnValue(true);
|
|
jest.mock('../../../Common/sources/storage/storage-base', () => {
|
|
const originalModule = jest.requireActual('../../../Common/sources/storage/storage-base');
|
|
return {
|
|
...originalModule,
|
|
needServeStatic: mockNeedServeStatic
|
|
};
|
|
});
|
|
const {cp} = require('fs/promises');
|
|
const http = require('http');
|
|
const https = require('https');
|
|
const fs = require('fs');
|
|
const {Readable} = require('stream');
|
|
|
|
const testFileData1 = 'test1';
|
|
const testFileData2 = 'test22';
|
|
const testFileData3 = 'test333';
|
|
const testFileData4 = testFileData3;
|
|
|
|
const express = require('express');
|
|
const operationContext = require('../../../Common/sources/operationContext');
|
|
const tenantManager = require('../../../Common/sources/tenantManager');
|
|
const storage = require('../../../Common/sources/storage/storage-base');
|
|
const utils = require('../../../Common/sources/utils');
|
|
const commonDefines = require('../../../Common/sources/commondefines');
|
|
const config = require('../../../Common/node_modules/config');
|
|
const staticRouter = require('../../../DocService/sources/routes/static');
|
|
|
|
const cfgCacheStorage = config.get('storage');
|
|
const cfgPersistentStorage = utils.deepMergeObjects({}, cfgCacheStorage, config.get('persistentStorage'));
|
|
|
|
const ctx = operationContext.global;
|
|
const PORT = 3457;
|
|
const rand = Math.floor(Math.random() * 1000000);
|
|
const testDir = 'DocService-DocsCoServer-storage-' + rand;
|
|
const baseUrl = `http://localhost:${PORT}`;
|
|
const urlType = commonDefines.c_oAscUrlTypes.Session;
|
|
const testFile1 = testDir + '/test1.txt';
|
|
const testFile2 = testDir + '/test2.txt';
|
|
const testFile3 = testDir + '/test3.txt';
|
|
const testFile4 = testDir + '/test4.txt';
|
|
const specialDirCache = '';
|
|
const specialDirForgotten = 'forgotten';
|
|
|
|
console.debug(`testDir: ${testDir}`);
|
|
|
|
let server;
|
|
|
|
beforeAll(async () => {
|
|
//start server to server static files generated by getSignedUrl
|
|
const app = express();
|
|
app.use('/', staticRouter);
|
|
server = app.listen(PORT, () => {
|
|
console.debug('listening on ' + PORT);
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (server) {
|
|
await new Promise(resolve => server.close(resolve));
|
|
}
|
|
});
|
|
|
|
function getStorageCfg(specialDir) {
|
|
return specialDir ? cfgPersistentStorage : cfgCacheStorage;
|
|
}
|
|
|
|
function request(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const module = url.startsWith('https') ? https : http;
|
|
const req = module.get(url, response => {
|
|
let data = '';
|
|
response.on('data', _data => (data += _data));
|
|
response.on('error', error => reject(error));
|
|
response.on('end', () => resolve(data));
|
|
});
|
|
|
|
req.on('error', error => reject(error));
|
|
});
|
|
}
|
|
function runTestForDir(ctx, isMultitenantMode, specialDir) {
|
|
const oldMultitenantMode = tenantManager.isMultitenantMode();
|
|
test('start listObjects', async () => {
|
|
//todo set in all tests do not rely on test order
|
|
tenantManager.setMultitenantMode(isMultitenantMode);
|
|
|
|
const list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list).toEqual([]);
|
|
});
|
|
test('putObject', async () => {
|
|
const buffer = Buffer.from(testFileData1);
|
|
const res = await storage.putObject(ctx, testFile1, buffer, buffer.length, specialDir);
|
|
expect(res).toEqual(undefined);
|
|
const list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile1].sort());
|
|
});
|
|
test('putObject-stream', async () => {
|
|
const buffer = Buffer.from(testFileData2);
|
|
const stream = Readable.from(buffer);
|
|
const res = await storage.putObject(ctx, testFile2, stream, buffer.length, specialDir);
|
|
expect(res).toEqual(undefined);
|
|
const list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile1, testFile2].sort());
|
|
});
|
|
if ('storage-fs' === getStorageCfg(specialDir).name) {
|
|
test('UploadObject', async () => {
|
|
const res = await storage.uploadObject(ctx, testFile3, 'createReadStream.txt', specialDir);
|
|
expect(res).toEqual(undefined);
|
|
expect(cp).toHaveBeenCalled();
|
|
const list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile1, testFile2, testFile3].sort());
|
|
});
|
|
} else {
|
|
test('uploadObject', async () => {
|
|
const readStream = Readable.from(testFileData3);
|
|
readStream.size = testFileData3.length;
|
|
const spy = jest.spyOn(fs, 'createReadStream').mockReturnValue(readStream);
|
|
const res = await storage.uploadObject(ctx, testFile3, 'createReadStream.txt', specialDir);
|
|
expect(res).toEqual(undefined);
|
|
const list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(spy).toHaveBeenCalled();
|
|
expect(list.sort()).toEqual([testFile1, testFile2, testFile3].sort());
|
|
spy.mockRestore();
|
|
});
|
|
|
|
//todo fails with storage-s3
|
|
test.skip('uploadObject - stream error handling', async () => {
|
|
const streamErrorMessage = new Error('Test stream error');
|
|
const mockStream = Readable.from(
|
|
(async function* () {
|
|
yield 'first chunk\n';
|
|
await new Promise(r => setTimeout(r, 5));
|
|
throw streamErrorMessage;
|
|
})()
|
|
);
|
|
mockStream.size = 1024;
|
|
|
|
const spy = jest.spyOn(fs, 'createReadStream').mockReturnValue(mockStream);
|
|
// Verify that the uploadObject function rejects when the stream emits an error
|
|
await expect(storage.uploadObject(ctx, 'test-error-file.txt', 'nonexistent.txt', specialDir)).rejects.toThrow(streamErrorMessage);
|
|
|
|
spy.mockRestore();
|
|
});
|
|
|
|
test.skip('uploadObject - non-existent file handling', async () => {
|
|
const nonExistentFile = 'definitely-does-not-exist-' + Date.now() + '.txt';
|
|
// Verify the file actually doesn't exist
|
|
expect(fs.existsSync(nonExistentFile)).toBe(false);
|
|
// Verify that uploadObject properly handles and propagates the error
|
|
await expect(storage.uploadObject(ctx, 'test-error-file.txt', nonExistentFile, specialDir)).rejects.toThrow(/ENOENT/);
|
|
});
|
|
}
|
|
test('copyObject', async () => {
|
|
const res = await storage.copyObject(ctx, testFile3, testFile4, specialDir, specialDir);
|
|
expect(res).toEqual(undefined);
|
|
// let buffer = Buffer.from(testFileData3);
|
|
// await storage.putObject(ctx, testFile3, buffer, buffer.length, specialDir);
|
|
const list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile1, testFile2, testFile3, testFile4].sort());
|
|
});
|
|
test('headObject', async () => {
|
|
let output;
|
|
output = await storage.headObject(ctx, testFile1, specialDir);
|
|
expect(output).toMatchObject({ContentLength: testFileData1.length});
|
|
|
|
output = await storage.headObject(ctx, testFile2, specialDir);
|
|
expect(output).toMatchObject({ContentLength: testFileData2.length});
|
|
|
|
output = await storage.headObject(ctx, testFile3, specialDir);
|
|
expect(output).toMatchObject({ContentLength: testFileData3.length});
|
|
|
|
output = await storage.headObject(ctx, testFile4, specialDir);
|
|
expect(output).toMatchObject({ContentLength: testFileData4.length});
|
|
});
|
|
test('getObject', async () => {
|
|
let output;
|
|
output = await storage.getObject(ctx, testFile1, specialDir);
|
|
expect(output.toString('utf8')).toEqual(testFileData1);
|
|
|
|
output = await storage.getObject(ctx, testFile2, specialDir);
|
|
expect(output.toString('utf8')).toEqual(testFileData2);
|
|
|
|
output = await storage.getObject(ctx, testFile3, specialDir);
|
|
expect(output.toString('utf8')).toEqual(testFileData3);
|
|
|
|
output = await storage.getObject(ctx, testFile4, specialDir);
|
|
expect(output.toString('utf8')).toEqual(testFileData4);
|
|
});
|
|
test('createReadStream', async () => {
|
|
let output, outputText;
|
|
|
|
output = await storage.createReadStream(ctx, testFile1, specialDir);
|
|
expect(output.contentLength).toEqual(testFileData1.length);
|
|
outputText = await utils.stream2Buffer(output.readStream);
|
|
expect(outputText.toString('utf8')).toEqual(testFileData1);
|
|
|
|
output = await storage.createReadStream(ctx, testFile2, specialDir);
|
|
expect(output.contentLength).toEqual(testFileData2.length);
|
|
outputText = await utils.stream2Buffer(output.readStream);
|
|
expect(outputText.toString('utf8')).toEqual(testFileData2);
|
|
|
|
output = await storage.createReadStream(ctx, testFile3, specialDir);
|
|
expect(output.contentLength).toEqual(testFileData3.length);
|
|
outputText = await utils.stream2Buffer(output.readStream);
|
|
expect(outputText.toString('utf8')).toEqual(testFileData3);
|
|
});
|
|
test('getSignedUrl', async () => {
|
|
let url, data;
|
|
url = await storage.getSignedUrl(ctx, baseUrl, testFile1, urlType, undefined, undefined, specialDir);
|
|
data = await request(url);
|
|
expect(data).toEqual(testFileData1);
|
|
|
|
url = await storage.getSignedUrl(ctx, baseUrl, testFile2, urlType, undefined, undefined, specialDir);
|
|
data = await request(url);
|
|
expect(data).toEqual(testFileData2);
|
|
|
|
url = await storage.getSignedUrl(ctx, baseUrl, testFile3, urlType, undefined, undefined, specialDir);
|
|
data = await request(url);
|
|
expect(data).toEqual(testFileData3);
|
|
|
|
url = await storage.getSignedUrl(ctx, baseUrl, testFile4, urlType, undefined, undefined, specialDir);
|
|
data = await request(url);
|
|
expect(data).toEqual(testFileData4);
|
|
});
|
|
test('getSignedUrls', async () => {
|
|
const urls = await storage.getSignedUrls(ctx, baseUrl, testDir, urlType, undefined, specialDir);
|
|
const data = [];
|
|
for (const i in urls) {
|
|
data.push(await request(urls[i]));
|
|
}
|
|
expect(data.sort()).toEqual([testFileData1, testFileData2, testFileData3, testFileData4].sort());
|
|
});
|
|
test('getSignedUrlsArrayByArray', async () => {
|
|
const urls = await storage.getSignedUrlsArrayByArray(ctx, baseUrl, [testFile1, testFile2], urlType, specialDir);
|
|
const data = [];
|
|
for (let i = 0; i < urls.length; ++i) {
|
|
data.push(await request(urls[i]));
|
|
}
|
|
expect(data.sort()).toEqual([testFileData1, testFileData2].sort());
|
|
});
|
|
test('getSignedUrlsByArray', async () => {
|
|
const urls = await storage.getSignedUrlsByArray(ctx, baseUrl, [testFile3, testFile4], undefined, urlType, specialDir);
|
|
const data = [];
|
|
for (const i in urls) {
|
|
data.push(await request(urls[i]));
|
|
}
|
|
expect(data.sort()).toEqual([testFileData3, testFileData4].sort());
|
|
});
|
|
test('getSignedUrl with direct URLs enabled', async () => {
|
|
const buffer = Buffer.from(testFileData1);
|
|
const res = await storage.putObject(ctx, testFile1, buffer, buffer.length, specialDirCache);
|
|
expect(res).toEqual(undefined);
|
|
|
|
const url = await storage.getSignedUrl(ctx, baseUrl, testFile1, urlType, undefined, undefined, specialDirCache, true);
|
|
const data = await request(url);
|
|
expect(data).toEqual(testFileData1);
|
|
|
|
if (cfgCacheStorage.name !== 'storage-fs') {
|
|
expect(url).toContain(cfgCacheStorage.endpoint);
|
|
expect(url).toContain(cfgCacheStorage.bucketName);
|
|
}
|
|
});
|
|
test('getSignedUrl with direct URLs disabled', async () => {
|
|
const buffer = Buffer.from(testFileData1);
|
|
const res = await storage.putObject(ctx, testFile1, buffer, buffer.length, specialDirCache);
|
|
expect(res).toEqual(undefined);
|
|
|
|
const url = await storage.getSignedUrl(ctx, baseUrl, testFile1, urlType, undefined, undefined, specialDirCache, false);
|
|
const data = await request(url);
|
|
expect(data).toEqual(testFileData1);
|
|
|
|
expect(url).toContain('md5');
|
|
expect(url).toContain('expires');
|
|
expect(url).toContain(cfgCacheStorage.storageFolderName);
|
|
});
|
|
test('deleteObject', async () => {
|
|
let list;
|
|
list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile1, testFile2, testFile3, testFile4].sort());
|
|
|
|
const res = await storage.deleteObject(ctx, testFile1, specialDir);
|
|
expect(res).toEqual(undefined);
|
|
|
|
list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile2, testFile3, testFile4].sort());
|
|
});
|
|
test('deletePath', async () => {
|
|
let list;
|
|
list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([testFile2, testFile3, testFile4].sort());
|
|
|
|
const res = await storage.deletePath(ctx, testDir, specialDir);
|
|
expect(res).toEqual(undefined);
|
|
|
|
list = await storage.listObjects(ctx, testDir, specialDir);
|
|
expect(list.sort()).toEqual([].sort());
|
|
|
|
tenantManager.setMultitenantMode(oldMultitenantMode);
|
|
});
|
|
}
|
|
|
|
// Assumed, that server is already up.
|
|
describe('storage common dir', () => {
|
|
runTestForDir(ctx, false, specialDirCache);
|
|
});
|
|
|
|
describe('storage forgotten dir', () => {
|
|
runTestForDir(ctx, false, specialDirForgotten);
|
|
});
|
|
|
|
describe('storage common dir with tenants', () => {
|
|
runTestForDir(ctx, true, specialDirCache);
|
|
});
|
|
|
|
describe('storage forgotten dir with tenants', () => {
|
|
runTestForDir(ctx, true, specialDirForgotten);
|
|
});
|
|
|
|
describe('storage mix common and forgotten dir', () => {
|
|
test('putObject', async () => {
|
|
tenantManager.setMultitenantMode(false);
|
|
|
|
let buffer = Buffer.from(testFileData1);
|
|
const res = await storage.putObject(ctx, testFile1, buffer, buffer.length, specialDirCache);
|
|
expect(res).toEqual(undefined);
|
|
let list = await storage.listObjects(ctx, testDir, specialDirCache);
|
|
expect(list.sort()).toEqual([testFile1].sort());
|
|
|
|
buffer = Buffer.from(testFileData2);
|
|
const res2 = await storage.putObject(ctx, testFile2, buffer, buffer.length, specialDirForgotten);
|
|
expect(res2).toEqual(undefined);
|
|
list = await storage.listObjects(ctx, testDir, specialDirForgotten);
|
|
expect(list.sort()).toEqual([testFile2].sort());
|
|
});
|
|
|
|
test('copyPath', async () => {
|
|
const res = await storage.copyPath(ctx, testDir, testDir, specialDirCache, specialDirForgotten);
|
|
expect(res).toEqual(undefined);
|
|
|
|
const list = await storage.listObjects(ctx, testDir, specialDirForgotten);
|
|
expect(list.sort()).toEqual([testFile1, testFile2].sort());
|
|
});
|
|
test('copyObject', async () => {
|
|
const res = await storage.copyObject(ctx, testFile2, testFile2, specialDirForgotten, specialDirCache);
|
|
expect(res).toEqual(undefined);
|
|
|
|
const list = await storage.listObjects(ctx, testDir, specialDirCache);
|
|
expect(list.sort()).toEqual([testFile1, testFile2].sort());
|
|
});
|
|
|
|
test('deletePath', async () => {
|
|
let list, res;
|
|
res = await storage.deletePath(ctx, testDir, specialDirCache);
|
|
expect(res).toEqual(undefined);
|
|
|
|
list = await storage.listObjects(ctx, testDir, specialDirCache);
|
|
expect(list.sort()).toEqual([].sort());
|
|
|
|
res = await storage.deletePath(ctx, testDir, specialDirForgotten);
|
|
expect(res).toEqual(undefined);
|
|
|
|
list = await storage.listObjects(ctx, testDir, specialDirForgotten);
|
|
expect(list.sort()).toEqual([].sort());
|
|
});
|
|
});
|