2019-12-16 06:20:22 -05:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
const sinon = require('sinon');
|
|
|
|
const chai = require('chai');
|
|
|
|
|
|
|
|
const should = chai.should();
|
|
|
|
const {
|
|
|
|
expect
|
|
|
|
} = chai;
|
|
|
|
|
|
|
|
const modulePath = "../../../app/js/AWSSDKPersistorManager.js";
|
|
|
|
const SandboxedModule = require('sandboxed-module');
|
|
|
|
|
|
|
|
describe("AWSSDKPersistorManager", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.settings = {
|
|
|
|
filestore: {
|
2015-11-20 08:38:23 -05:00
|
|
|
backend: "aws-sdk"
|
2019-12-16 06:20:22 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
this.s3 = {
|
|
|
|
upload: sinon.stub(),
|
|
|
|
getObject: sinon.stub(),
|
|
|
|
copyObject: sinon.stub(),
|
|
|
|
deleteObject: sinon.stub(),
|
|
|
|
listObjects: sinon.stub(),
|
|
|
|
deleteObjects: sinon.stub(),
|
2015-11-20 08:38:23 -05:00
|
|
|
headObject: sinon.stub()
|
2019-12-16 06:20:22 -05:00
|
|
|
};
|
|
|
|
this.awssdk =
|
|
|
|
{S3: sinon.stub().returns(this.s3)};
|
|
|
|
|
|
|
|
this.requires = {
|
|
|
|
"aws-sdk": this.awssdk,
|
|
|
|
"settings-sharelatex": this.settings,
|
|
|
|
"logger-sharelatex": {
|
|
|
|
log() {},
|
|
|
|
err() {}
|
|
|
|
},
|
|
|
|
"fs": (this.fs =
|
|
|
|
{createReadStream: sinon.stub()}),
|
|
|
|
"./Errors": (this.Errors =
|
|
|
|
{NotFoundError: sinon.stub()})
|
|
|
|
};
|
|
|
|
this.key = "my/key";
|
|
|
|
this.bucketName = "my-bucket";
|
|
|
|
this.error = "my error";
|
|
|
|
return this.AWSSDKPersistorManager = SandboxedModule.require(modulePath, {requires: this.requires});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("sendFile", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.stream = {};
|
|
|
|
this.fsPath = "/usr/local/some/file";
|
|
|
|
return this.fs.createReadStream.returns(this.stream);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should put the file with s3.upload", function(done) {
|
|
|
|
this.s3.upload.callsArgWith(1);
|
|
|
|
return this.AWSSDKPersistorManager.sendFile(this.bucketName, this.key, this.fsPath, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.upload.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.upload.calledWith({Bucket: this.bucketName, Key: this.key, Body: this.stream}))
|
|
|
|
, "called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should dispatch the error from s3.upload", function(done) {
|
|
|
|
this.s3.upload.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.sendFile(this.bucketName, this.key, this.fsPath, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("sendStream", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
return this.stream = {};});
|
|
|
|
|
|
|
|
it("should put the file with s3.upload", function(done) {
|
|
|
|
this.s3.upload.callsArgWith(1);
|
|
|
|
return this.AWSSDKPersistorManager.sendStream(this.bucketName, this.key, this.stream, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.upload.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.upload.calledWith({Bucket: this.bucketName, Key: this.key, Body: this.stream})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should dispatch the error from s3.upload", function(done) {
|
|
|
|
this.s3.upload.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.sendStream(this.bucketName, this.key, this.stream, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("getFileStream", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.opts = {};
|
|
|
|
this.stream = {};
|
|
|
|
this.read_stream =
|
|
|
|
{on: (this.read_stream_on = sinon.stub())};
|
|
|
|
this.object =
|
|
|
|
{createReadStream: sinon.stub().returns(this.read_stream)};
|
|
|
|
return this.s3.getObject.returns(this.object);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should return a stream from s3.getObject", function(done) {
|
|
|
|
this.read_stream_on.withArgs('readable').callsArgWith(1);
|
|
|
|
|
|
|
|
return this.AWSSDKPersistorManager.getFileStream(this.bucketName, this.key, this.opts, (err, stream) => {
|
|
|
|
expect(this.read_stream_on.calledTwice);
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(stream, "returned the stream").to.equal(this.read_stream);
|
|
|
|
expect((this.s3.getObject.calledWith({Bucket: this.bucketName, Key: this.key})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("with start and end options", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
return this.opts = {
|
|
|
|
start: 0,
|
2015-11-20 08:38:23 -05:00
|
|
|
end: 8
|
2019-12-16 06:20:22 -05:00
|
|
|
};
|
|
|
|
});
|
|
|
|
return it("should pass headers to the s3.GetObject", function(done) {
|
|
|
|
this.read_stream_on.withArgs('readable').callsArgWith(1);
|
|
|
|
this.AWSSDKPersistorManager.getFileStream(this.bucketName, this.key, this.opts, (err, stream) => {
|
|
|
|
return expect((this.s3.getObject.calledWith({Bucket: this.bucketName, Key: this.key, Range: 'bytes=0-8'})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
});
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("error conditions", function() {
|
|
|
|
describe("when the file doesn't exist", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.error = new Error();
|
|
|
|
return this.error.code = 'NoSuchKey';
|
|
|
|
});
|
|
|
|
return it("should produce a NotFoundError", function(done) {
|
|
|
|
this.read_stream_on.withArgs('error').callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.getFileStream(this.bucketName, this.key, this.opts, (err, stream) => {
|
|
|
|
expect(stream).to.not.be.ok;
|
|
|
|
expect(err).to.be.ok;
|
|
|
|
expect(err instanceof this.Errors.NotFoundError, "error is a correct instance").to.equal(true);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("when there is some other error", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
return this.error = new Error();
|
|
|
|
});
|
|
|
|
return it("should dispatch the error from s3 object stream", function(done) {
|
|
|
|
this.read_stream_on.withArgs('error').callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.getFileStream(this.bucketName, this.key, this.opts, (err, stream) => {
|
|
|
|
expect(stream).to.not.be.ok;
|
|
|
|
expect(err).to.be.ok;
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("copyFile", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
this.destKey = "some/key";
|
|
|
|
return this.stream = {};});
|
|
|
|
|
|
|
|
it("should copy the file with s3.copyObject", function(done) {
|
|
|
|
this.s3.copyObject.callsArgWith(1);
|
|
|
|
return this.AWSSDKPersistorManager.copyFile(this.bucketName, this.key, this.destKey, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.copyObject.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.copyObject.calledWith({Bucket: this.bucketName, Key: this.destKey, CopySource: this.bucketName + '/' + this.key})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should dispatch the error from s3.copyObject", function(done) {
|
|
|
|
this.s3.copyObject.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.copyFile(this.bucketName, this.key, this.destKey, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("deleteFile", function() {
|
|
|
|
it("should delete the file with s3.deleteObject", function(done) {
|
|
|
|
this.s3.deleteObject.callsArgWith(1);
|
|
|
|
return this.AWSSDKPersistorManager.deleteFile(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.deleteObject.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.deleteObject.calledWith({Bucket: this.bucketName, Key: this.key})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should dispatch the error from s3.deleteObject", function(done) {
|
|
|
|
this.s3.deleteObject.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.deleteFile(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("deleteDirectory", function() {
|
|
|
|
|
|
|
|
it("should list the directory content using s3.listObjects", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, null, {Contents: []});
|
|
|
|
return this.AWSSDKPersistorManager.deleteDirectory(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.listObjects.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.listObjects.calledWith({Bucket: this.bucketName, Prefix: this.key})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should dispatch the error from s3.listObjects", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.deleteDirectory(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("with directory content", function() {
|
|
|
|
beforeEach(function() {
|
|
|
|
return this.fileList = [
|
|
|
|
{Key: 'foo'}
|
|
|
|
, { Key: 'bar'
|
2015-11-20 08:38:23 -05:00
|
|
|
, Key: 'baz'
|
2019-12-16 06:20:22 -05:00
|
|
|
}
|
|
|
|
];});
|
|
|
|
|
|
|
|
it("should forward the file keys to s3.deleteObjects", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, null, {Contents: this.fileList});
|
|
|
|
this.s3.deleteObjects.callsArgWith(1);
|
|
|
|
return this.AWSSDKPersistorManager.deleteDirectory(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.deleteObjects.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.deleteObjects.calledWith({
|
|
|
|
Bucket: this.bucketName,
|
|
|
|
Delete: {
|
|
|
|
Quiet: true,
|
|
|
|
Objects: this.fileList
|
|
|
|
}})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should dispatch the error from s3.deleteObjects", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, null, {Contents: this.fileList});
|
|
|
|
this.s3.deleteObjects.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.deleteDirectory(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
describe("checkIfFileExists", function() {
|
|
|
|
|
|
|
|
it("should check for the file with s3.headObject", function(done) {
|
|
|
|
this.s3.headObject.callsArgWith(1, null, {});
|
|
|
|
return this.AWSSDKPersistorManager.checkIfFileExists(this.bucketName, this.key, (err, exists) => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.headObject.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.headObject.calledWith({Bucket: this.bucketName, Key: this.key})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should return false on an inexistant file", function(done) {
|
|
|
|
this.s3.headObject.callsArgWith(1, null, {});
|
|
|
|
return this.AWSSDKPersistorManager.checkIfFileExists(this.bucketName, this.key, (err, exists) => {
|
|
|
|
expect(exists).to.be.false;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should return true on an existing file", function(done) {
|
|
|
|
this.s3.headObject.callsArgWith(1, null, {ETag: "etag"});
|
|
|
|
return this.AWSSDKPersistorManager.checkIfFileExists(this.bucketName, this.key, (err, exists) => {
|
|
|
|
expect(exists).to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should dispatch the error from s3.headObject", function(done) {
|
|
|
|
this.s3.headObject.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.checkIfFileExists(this.bucketName, this.key, (err, exists) => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return describe("directorySize", function() {
|
|
|
|
|
|
|
|
it("should list the directory content using s3.listObjects", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, null, {Contents: []});
|
|
|
|
return this.AWSSDKPersistorManager.directorySize(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.not.be.ok;
|
|
|
|
expect(this.s3.listObjects.calledOnce, "called only once").to.be.true;
|
|
|
|
expect((this.s3.listObjects.calledWith({Bucket: this.bucketName, Prefix: this.key})),
|
|
|
|
"called with correct arguments").to.be.true;
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should dispatch the error from s3.listObjects", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, this.error);
|
|
|
|
return this.AWSSDKPersistorManager.directorySize(this.bucketName, this.key, err => {
|
|
|
|
expect(err).to.equal(this.error);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return it("should sum directory files sizes", function(done) {
|
|
|
|
this.s3.listObjects.callsArgWith(1, null, {Contents: [ { Size: 1024 }, { Size: 2048 }]});
|
|
|
|
return this.AWSSDKPersistorManager.directorySize(this.bucketName, this.key, (err, size) => {
|
|
|
|
expect(size).to.equal(3072);
|
|
|
|
return done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|