Просмотр исходного кода

Prettier: convert test/unit decaffeinated files to Prettier format

Simon Detheridge 6 лет назад
Родитель
Сommit
eae7d28495

+ 496 - 348
services/filestore/test/unit/js/AWSSDKPersistorManagerTests.js

@@ -11,351 +11,499 @@
  * 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: {
-				backend: "aws-sdk"
-			}
-		};
-		this.s3 = {
-			upload: sinon.stub(),
-			getObject: sinon.stub(),
-			copyObject: sinon.stub(),
-			deleteObject: sinon.stub(),
-			listObjects: sinon.stub(),
-			deleteObjects: sinon.stub(),
-			headObject: sinon.stub()
-		};
-		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,
-					end: 8
-				};
-			});
-			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'
-				, Key: 'baz'
-			}
-				];});
-
-			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();
-			});
-		});
-	});
-});
+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: {
+        backend: 'aws-sdk'
+      }
+    }
+    this.s3 = {
+      upload: sinon.stub(),
+      getObject: sinon.stub(),
+      copyObject: sinon.stub(),
+      deleteObject: sinon.stub(),
+      listObjects: sinon.stub(),
+      deleteObjects: sinon.stub(),
+      headObject: sinon.stub()
+    }
+    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,
+          end: 8
+        })
+      })
+      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', Key: 'baz' }])
+      })
+
+      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()
+        }
+      )
+    })
+  })
+})

+ 84 - 90
services/filestore/test/unit/js/BucketControllerTests.js

@@ -9,98 +9,92 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/BucketController.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/BucketController.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("BucketController", function() {
+describe('BucketController', function() {
+  beforeEach(function() {
+    this.PersistorManager = {
+      sendStream: sinon.stub(),
+      copyFile: sinon.stub(),
+      deleteFile: sinon.stub()
+    }
 
-	beforeEach(function() {
-		this.PersistorManager = {
-			sendStream: sinon.stub(),
-			copyFile: sinon.stub(),
-			deleteFile:sinon.stub()
-		};
+    this.settings = {
+      s3: {
+        buckets: {
+          user_files: 'user_files'
+        }
+      },
+      filestore: {
+        backend: 's3',
+        s3: {
+          secret: 'secret',
+          key: 'this_key'
+        }
+      }
+    }
 
-		this.settings = {
-			s3: {
-				buckets: {
-					user_files:"user_files"
-				}
-			},
-			filestore: {
-				backend: "s3",
-				s3: {
-					secret: "secret",
-					key: "this_key"
-				}
-			}
-		};
+    this.FileHandler = {
+      getFile: sinon.stub(),
+      deleteFile: sinon.stub(),
+      insertFile: sinon.stub(),
+      getDirectorySize: sinon.stub()
+    }
+    this.LocalFileWriter = {}
+    this.controller = SandboxedModule.require(modulePath, {
+      requires: {
+        './LocalFileWriter': this.LocalFileWriter,
+        './FileHandler': this.FileHandler,
+        './PersistorManager': this.PersistorManager,
+        'settings-sharelatex': this.settings,
+        'metrics-sharelatex': {
+          inc() {}
+        },
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        }
+      }
+    })
+    this.project_id = 'project_id'
+    this.file_id = 'file_id'
+    this.bucket = 'user_files'
+    this.key = `${this.project_id}/${this.file_id}`
+    this.req = {
+      query: {},
+      params: {
+        bucket: this.bucket,
+        0: this.key
+      },
+      headers: {}
+    }
+    this.res = { setHeader() {} }
+    return (this.fileStream = {})
+  })
 
-		this.FileHandler = {
-			getFile: sinon.stub(),
-			deleteFile: sinon.stub(),
-			insertFile: sinon.stub(),
-			getDirectorySize: sinon.stub()
-		};
-		this.LocalFileWriter = {};
-		this.controller = SandboxedModule.require(modulePath, { requires: {
-			"./LocalFileWriter":this.LocalFileWriter,
-			"./FileHandler": this.FileHandler,
-			"./PersistorManager":this.PersistorManager,
-			"settings-sharelatex": this.settings,
-			"metrics-sharelatex": { 
-				inc() {}
-			},
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			}
-		}
-	}
-		);
-		this.project_id = "project_id";
-		this.file_id = "file_id";
-		this.bucket = "user_files";
-		this.key = `${this.project_id}/${this.file_id}`;
-		this.req = {
-			query:{},
-			params: {
-				bucket: this.bucket,
-				0: this.key
-			},
-			headers: {}
-		};
-		this.res =
-			{setHeader() {}};
-		return this.fileStream = {};});
+  return describe('getFile', function() {
+    it('should pipe the stream', function(done) {
+      this.FileHandler.getFile.callsArgWith(3, null, this.fileStream)
+      this.fileStream.pipe = res => {
+        res.should.equal(this.res)
+        return done()
+      }
+      return this.controller.getFile(this.req, this.res)
+    })
 
-	return describe("getFile", function() {
-
-		it("should pipe the stream", function(done){
-			this.FileHandler.getFile.callsArgWith(3, null, this.fileStream);
-			this.fileStream.pipe = res=> {
-				res.should.equal(this.res);
-				return done();
-			};
-			return this.controller.getFile(this.req, this.res);
-		});
-
-		return it("should send a 500 if there is a problem", function(done){
-			this.FileHandler.getFile.callsArgWith(3, "error");
-			this.res.send = code=> {
-				code.should.equal(500);
-				return done();
-			};
-			return this.controller.getFile(this.req, this.res);
-		});
-	});
-});
+    return it('should send a 500 if there is a problem', function(done) {
+      this.FileHandler.getFile.callsArgWith(3, 'error')
+      this.res.send = code => {
+        code.should.equal(500)
+        return done()
+      }
+      return this.controller.getFile(this.req, this.res)
+    })
+  })
+})

+ 457 - 334
services/filestore/test/unit/js/FSPersistorManagerTests.js

@@ -11,369 +11,492 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-  assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const {
-  should
-} = chai;
-const {
-  expect
-} = chai;
-const modulePath = "../../../app/js/FSPersistorManager.js";
-const SandboxedModule = require('sandboxed-module');
-const fs = require("fs");
-const response = require("response");
-
-describe("FSPersistorManagerTests", function() {
-
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const { should } = chai
+const { expect } = chai
+const modulePath = '../../../app/js/FSPersistorManager.js'
+const SandboxedModule = require('sandboxed-module')
+const fs = require('fs')
+const response = require('response')
+
+describe('FSPersistorManagerTests', function() {
   beforeEach(function() {
     this.Fs = {
-      rename:sinon.stub(),
-      createReadStream:sinon.stub(),
-      createWriteStream:sinon.stub(),
-      unlink:sinon.stub(),
-      rmdir:sinon.stub(),
-      exists:sinon.stub(),
-      readdir:sinon.stub(),
-      open:sinon.stub(),
-      openSync:sinon.stub(),
-      fstatSync:sinon.stub(),
-      closeSync:sinon.stub(),
-      stat:sinon.stub()
-    };
-    this.Rimraf = sinon.stub();
+      rename: sinon.stub(),
+      createReadStream: sinon.stub(),
+      createWriteStream: sinon.stub(),
+      unlink: sinon.stub(),
+      rmdir: sinon.stub(),
+      exists: sinon.stub(),
+      readdir: sinon.stub(),
+      open: sinon.stub(),
+      openSync: sinon.stub(),
+      fstatSync: sinon.stub(),
+      closeSync: sinon.stub(),
+      stat: sinon.stub()
+    }
+    this.Rimraf = sinon.stub()
     this.LocalFileWriter = {
       writeStream: sinon.stub(),
       deleteFile: sinon.stub()
-    };
+    }
     this.requires = {
-      "./LocalFileWriter":this.LocalFileWriter,
-      "fs":this.Fs,
-      "logger-sharelatex": {
+      './LocalFileWriter': this.LocalFileWriter,
+      fs: this.Fs,
+      'logger-sharelatex': {
         log() {},
         err() {}
       },
-      "response":response,
-      "rimraf":this.Rimraf,
-      "./Errors": (this.Errors =
-        {NotFoundError: sinon.stub()})
-    };
-    this.location = "/tmp";
-    this.name1 = "530f2407e7ef165704000007/530f838b46d9a9e859000008";
-    this.name1Filtered ="530f2407e7ef165704000007_530f838b46d9a9e859000008";
-    this.name2 = "second_file";
-    this.error = "error_message";
-    return this.FSPersistorManager = SandboxedModule.require(modulePath, {requires: this.requires});
-  });
-
-  describe("sendFile", function() {
+      response: response,
+      rimraf: this.Rimraf,
+      './Errors': (this.Errors = { NotFoundError: sinon.stub() })
+    }
+    this.location = '/tmp'
+    this.name1 = '530f2407e7ef165704000007/530f838b46d9a9e859000008'
+    this.name1Filtered = '530f2407e7ef165704000007_530f838b46d9a9e859000008'
+    this.name2 = 'second_file'
+    this.error = 'error_message'
+    return (this.FSPersistorManager = SandboxedModule.require(modulePath, {
+      requires: this.requires
+    }))
+  })
+
+  describe('sendFile', function() {
     beforeEach(function() {
-      return this.Fs.createReadStream = sinon.stub().returns({
+      return (this.Fs.createReadStream = sinon.stub().returns({
         on() {},
         pipe() {}
-      });
-    });
+      }))
+    })
 
-    it("should copy the file", function(done) {
-      this.Fs.createWriteStream =sinon.stub().returns({
+    it('should copy the file', function(done) {
+      this.Fs.createWriteStream = sinon.stub().returns({
         on(event, handler) {
-          if (event === 'finish') { return process.nextTick(handler); }
+          if (event === 'finish') {
+            return process.nextTick(handler)
+          }
         }
-      });
-      return this.FSPersistorManager.sendFile(this.location, this.name1, this.name2, err=> {
-        this.Fs.createReadStream.calledWith(this.name2).should.equal(true);
-        this.Fs.createWriteStream.calledWith(`${this.location}/${this.name1Filtered}` ).should.equal(true);
-        return done();
-      });
-    });
-
-    return it("should return an error if the file cannot be stored", function(done) {
-      this.Fs.createWriteStream =sinon.stub().returns({
-        on: (event, handler) => {
-         if (event === 'error') {
-          return process.nextTick(() => {
-            return handler(this.error);
-          });
+      })
+      return this.FSPersistorManager.sendFile(
+        this.location,
+        this.name1,
+        this.name2,
+        err => {
+          this.Fs.createReadStream.calledWith(this.name2).should.equal(true)
+          this.Fs.createWriteStream
+            .calledWith(`${this.location}/${this.name1Filtered}`)
+            .should.equal(true)
+          return done()
         }
-       }
-      });
-      return this.FSPersistorManager.sendFile(this.location, this.name1, this.name2, err=> {
-        this.Fs.createReadStream.calledWith(this.name2).should.equal(true);
-        this.Fs.createWriteStream.calledWith(`${this.location}/${this.name1Filtered}` ).should.equal(true);
-        err.should.equal(this.error);
-        return done();
-      });
-    });
-  });
-
-  describe("sendStream", function() {
-    beforeEach(function() {
-      this.FSPersistorManager.sendFile = sinon.stub().callsArgWith(3);
-      this.LocalFileWriter.writeStream.callsArgWith(2, null, this.name1);
-      this.LocalFileWriter.deleteFile.callsArg(1);
-      return this.SourceStream =
-        {on() {}};
-    });
-
-    it("should sent stream to LocalFileWriter", function(done){
-      return this.FSPersistorManager.sendStream(this.location, this.name1, this.SourceStream, () => {
-        this.LocalFileWriter.writeStream.calledWith(this.SourceStream).should.equal(true);
-        return done();
-      });
-    });
-
-    it("should return the error from LocalFileWriter", function(done){
-      this.LocalFileWriter.writeStream.callsArgWith(2, this.error);
-      return this.FSPersistorManager.sendStream(this.location, this.name1, this.SourceStream, err=> {
-        err.should.equal(this.error);
-        return done();
-      });
-    });
-
-    return it("should send the file to the filestore", function(done){
-      this.LocalFileWriter.writeStream.callsArgWith(2);
-      return this.FSPersistorManager.sendStream(this.location, this.name1, this.SourceStream, err=> {
-        this.FSPersistorManager.sendFile.called.should.equal(true);
-        return done();
-      });
-    });
-  });
-
-  describe("getFileStream", function() {
-    beforeEach(function() {
-      return this.opts = {};});
+      )
+    })
 
-    it("should use correct file location", function(done) {
-      this.FSPersistorManager.getFileStream(this.location, this.name1, this.opts, (err,res) => {});
-      this.Fs.open.calledWith(`${this.location}/${this.name1Filtered}`).should.equal(true);
-      return done();
-    });
+    return it('should return an error if the file cannot be stored', function(done) {
+      this.Fs.createWriteStream = sinon.stub().returns({
+        on: (event, handler) => {
+          if (event === 'error') {
+            return process.nextTick(() => {
+              return handler(this.error)
+            })
+          }
+        }
+      })
+      return this.FSPersistorManager.sendFile(
+        this.location,
+        this.name1,
+        this.name2,
+        err => {
+          this.Fs.createReadStream.calledWith(this.name2).should.equal(true)
+          this.Fs.createWriteStream
+            .calledWith(`${this.location}/${this.name1Filtered}`)
+            .should.equal(true)
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+  })
 
-    describe("with start and end options", function() {
+  describe('sendStream', function() {
+    beforeEach(function() {
+      this.FSPersistorManager.sendFile = sinon.stub().callsArgWith(3)
+      this.LocalFileWriter.writeStream.callsArgWith(2, null, this.name1)
+      this.LocalFileWriter.deleteFile.callsArg(1)
+      return (this.SourceStream = { on() {} })
+    })
+
+    it('should sent stream to LocalFileWriter', function(done) {
+      return this.FSPersistorManager.sendStream(
+        this.location,
+        this.name1,
+        this.SourceStream,
+        () => {
+          this.LocalFileWriter.writeStream
+            .calledWith(this.SourceStream)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
+
+    it('should return the error from LocalFileWriter', function(done) {
+      this.LocalFileWriter.writeStream.callsArgWith(2, this.error)
+      return this.FSPersistorManager.sendStream(
+        this.location,
+        this.name1,
+        this.SourceStream,
+        err => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+
+    return it('should send the file to the filestore', function(done) {
+      this.LocalFileWriter.writeStream.callsArgWith(2)
+      return this.FSPersistorManager.sendStream(
+        this.location,
+        this.name1,
+        this.SourceStream,
+        err => {
+          this.FSPersistorManager.sendFile.called.should.equal(true)
+          return done()
+        }
+      )
+    })
+  })
 
+  describe('getFileStream', function() {
+    beforeEach(function() {
+      return (this.opts = {})
+    })
+
+    it('should use correct file location', function(done) {
+      this.FSPersistorManager.getFileStream(
+        this.location,
+        this.name1,
+        this.opts,
+        (err, res) => {}
+      )
+      this.Fs.open
+        .calledWith(`${this.location}/${this.name1Filtered}`)
+        .should.equal(true)
+      return done()
+    })
+
+    describe('with start and end options', function() {
       beforeEach(function() {
-        this.fd = 2019;
-        this.opts_in = {start: 0, end: 8};
-        this.opts = {start: 0, end: 8, fd: this.fd};
-        return this.Fs.open.callsArgWith(2, null, this.fd);
-      });
+        this.fd = 2019
+        this.opts_in = { start: 0, end: 8 }
+        this.opts = { start: 0, end: 8, fd: this.fd }
+        return this.Fs.open.callsArgWith(2, null, this.fd)
+      })
 
       return it('should pass the options to createReadStream', function(done) {
-        this.FSPersistorManager.getFileStream(this.location, this.name1, this.opts_in, (err,res)=> {});
-        this.Fs.createReadStream.calledWith(null, this.opts).should.equal(true);
-        return done();
-      });
-    });
-
-    return describe("error conditions", function() {
-
-      describe("when the file does not exist", function() {
-
+        this.FSPersistorManager.getFileStream(
+          this.location,
+          this.name1,
+          this.opts_in,
+          (err, res) => {}
+        )
+        this.Fs.createReadStream.calledWith(null, this.opts).should.equal(true)
+        return done()
+      })
+    })
+
+    return describe('error conditions', function() {
+      describe('when the file does not exist', function() {
         beforeEach(function() {
-          this.fakeCode = 'ENOENT';
-          const err = new Error();
-          err.code = this.fakeCode;
-          return this.Fs.open.callsArgWith(2, err, null);
-        });
-
-        return it("should give a NotFoundError", function(done) {
-          return this.FSPersistorManager.getFileStream(this.location, this.name1, this.opts, (err,res)=> {
-            expect(res).to.equal(null);
-            expect(err).to.not.equal(null);
-            expect(err instanceof this.Errors.NotFoundError).to.equal(true);
-            return done();
-          });
-        });
-      });
-
-      return describe("when some other error happens", function() {
-
+          this.fakeCode = 'ENOENT'
+          const err = new Error()
+          err.code = this.fakeCode
+          return this.Fs.open.callsArgWith(2, err, null)
+        })
+
+        return it('should give a NotFoundError', function(done) {
+          return this.FSPersistorManager.getFileStream(
+            this.location,
+            this.name1,
+            this.opts,
+            (err, res) => {
+              expect(res).to.equal(null)
+              expect(err).to.not.equal(null)
+              expect(err instanceof this.Errors.NotFoundError).to.equal(true)
+              return done()
+            }
+          )
+        })
+      })
+
+      return describe('when some other error happens', function() {
         beforeEach(function() {
-          this.fakeCode = 'SOMETHINGHORRIBLE';
-          const err = new Error();
-          err.code = this.fakeCode;
-          return this.Fs.open.callsArgWith(2, err, null);
-        });
-
-        return it("should give an Error", function(done) {
-          return this.FSPersistorManager.getFileStream(this.location, this.name1, this.opts, (err,res)=> {
-            expect(res).to.equal(null);
-            expect(err).to.not.equal(null);
-            expect(err instanceof Error).to.equal(true);
-            return done();
-          });
-        });
-      });
-    });
-  });
-
-  describe("getFileSize", function() {
-    it("should return the file size", function(done) {
-      const expectedFileSize = 75382;
-      this.Fs.stat.yields(new Error("fs.stat got unexpected arguments"));
-      this.Fs.stat.withArgs(`${this.location}/${this.name1Filtered}`)
-        .yields(null, { size: expectedFileSize });
-
-      return this.FSPersistorManager.getFileSize(this.location, this.name1, (err, fileSize) => {
-        if (err != null) {
-          return done(err);
+          this.fakeCode = 'SOMETHINGHORRIBLE'
+          const err = new Error()
+          err.code = this.fakeCode
+          return this.Fs.open.callsArgWith(2, err, null)
+        })
+
+        return it('should give an Error', function(done) {
+          return this.FSPersistorManager.getFileStream(
+            this.location,
+            this.name1,
+            this.opts,
+            (err, res) => {
+              expect(res).to.equal(null)
+              expect(err).to.not.equal(null)
+              expect(err instanceof Error).to.equal(true)
+              return done()
+            }
+          )
+        })
+      })
+    })
+  })
+
+  describe('getFileSize', function() {
+    it('should return the file size', function(done) {
+      const expectedFileSize = 75382
+      this.Fs.stat.yields(new Error('fs.stat got unexpected arguments'))
+      this.Fs.stat
+        .withArgs(`${this.location}/${this.name1Filtered}`)
+        .yields(null, { size: expectedFileSize })
+
+      return this.FSPersistorManager.getFileSize(
+        this.location,
+        this.name1,
+        (err, fileSize) => {
+          if (err != null) {
+            return done(err)
+          }
+          expect(fileSize).to.equal(expectedFileSize)
+          return done()
         }
-        expect(fileSize).to.equal(expectedFileSize);
-        return done();
-      });
-    });
-
-    it("should throw a NotFoundError if the file does not exist", function(done) {
-      const error = new Error();
-      error.code = "ENOENT";
-      this.Fs.stat.yields(error);
-
-      return this.FSPersistorManager.getFileSize(this.location, this.name1, (err, fileSize) => {
-        expect(err).to.be.instanceof(this.Errors.NotFoundError);
-        return done();
-      });
-    });
-
-    return it("should rethrow any other error", function(done) {
-      const error = new Error();
-      this.Fs.stat.yields(error);
-
-      return this.FSPersistorManager.getFileSize(this.location, this.name1, (err, fileSize) => {
-        expect(err).to.equal(error);
-        return done();
-      });
-    });
-  });
-
-  describe("copyFile", function() {
+      )
+    })
+
+    it('should throw a NotFoundError if the file does not exist', function(done) {
+      const error = new Error()
+      error.code = 'ENOENT'
+      this.Fs.stat.yields(error)
+
+      return this.FSPersistorManager.getFileSize(
+        this.location,
+        this.name1,
+        (err, fileSize) => {
+          expect(err).to.be.instanceof(this.Errors.NotFoundError)
+          return done()
+        }
+      )
+    })
+
+    return it('should rethrow any other error', function(done) {
+      const error = new Error()
+      this.Fs.stat.yields(error)
+
+      return this.FSPersistorManager.getFileSize(
+        this.location,
+        this.name1,
+        (err, fileSize) => {
+          expect(err).to.equal(error)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('copyFile', function() {
     beforeEach(function() {
-      this.ReadStream= {
+      this.ReadStream = {
         on() {},
-        pipe:sinon.stub()
-      };
-      this.WriteStream=
-        {on() {}};
-      this.Fs.createReadStream.returns(this.ReadStream);
-      return this.Fs.createWriteStream.returns(this.WriteStream);
-    });
-
-    it("Should open the source for reading", function(done) {
-      this.FSPersistorManager.copyFile(this.location, this.name1, this.name2, function() {});
-      this.Fs.createReadStream.calledWith(`${this.location}/${this.name1Filtered}`).should.equal(true);
-      return done();
-    });
-
-    it("Should open the target for writing", function(done) {
-      this.FSPersistorManager.copyFile(this.location, this.name1, this.name2, function() {});
-      this.Fs.createWriteStream.calledWith(`${this.location}/${this.name2}`).should.equal(true);
-      return done();
-    });
-
-    return it("Should pipe the source to the target", function(done) {
-      this.FSPersistorManager.copyFile(this.location, this.name1, this.name2, function() {});
-      this.ReadStream.pipe.calledWith(this.WriteStream).should.equal(true);
-      return done();
-    });
-  });
-
-  describe("deleteFile", function() {
-    beforeEach(function() {
-      return this.Fs.unlink.callsArgWith(1,this.error);
-    });
-
-    it("Should call unlink with correct options", function(done) {
-      return this.FSPersistorManager.deleteFile(this.location, this.name1, err => {
-        this.Fs.unlink.calledWith(`${this.location}/${this.name1Filtered}`).should.equal(true);
-        return done();
-      });
-    });
-
-    return it("Should propogate the error", function(done) {
-      return this.FSPersistorManager.deleteFile(this.location, this.name1, err => {
-        err.should.equal(this.error);
-        return done();
-      });
-    });
-  });
-
-
-  describe("deleteDirectory", function() {
+        pipe: sinon.stub()
+      }
+      this.WriteStream = { on() {} }
+      this.Fs.createReadStream.returns(this.ReadStream)
+      return this.Fs.createWriteStream.returns(this.WriteStream)
+    })
+
+    it('Should open the source for reading', function(done) {
+      this.FSPersistorManager.copyFile(
+        this.location,
+        this.name1,
+        this.name2,
+        function() {}
+      )
+      this.Fs.createReadStream
+        .calledWith(`${this.location}/${this.name1Filtered}`)
+        .should.equal(true)
+      return done()
+    })
+
+    it('Should open the target for writing', function(done) {
+      this.FSPersistorManager.copyFile(
+        this.location,
+        this.name1,
+        this.name2,
+        function() {}
+      )
+      this.Fs.createWriteStream
+        .calledWith(`${this.location}/${this.name2}`)
+        .should.equal(true)
+      return done()
+    })
+
+    return it('Should pipe the source to the target', function(done) {
+      this.FSPersistorManager.copyFile(
+        this.location,
+        this.name1,
+        this.name2,
+        function() {}
+      )
+      this.ReadStream.pipe.calledWith(this.WriteStream).should.equal(true)
+      return done()
+    })
+  })
+
+  describe('deleteFile', function() {
     beforeEach(function() {
-      return this.Rimraf.callsArgWith(1,this.error);
-    });
-
-    it("Should call rmdir(rimraf) with correct options", function(done) {
-      return this.FSPersistorManager.deleteDirectory(this.location, this.name1, err => {
-        this.Rimraf.calledWith(`${this.location}/${this.name1Filtered}`).should.equal(true);
-        return done();
-      });
-    });
-
-    return it("Should propogate the error", function(done) {
-      return this.FSPersistorManager.deleteDirectory(this.location, this.name1, err => {
-        err.should.equal(this.error);
-        return done();
-      });
-    });
-  });
-
-  describe("checkIfFileExists", function() {
+      return this.Fs.unlink.callsArgWith(1, this.error)
+    })
+
+    it('Should call unlink with correct options', function(done) {
+      return this.FSPersistorManager.deleteFile(
+        this.location,
+        this.name1,
+        err => {
+          this.Fs.unlink
+            .calledWith(`${this.location}/${this.name1Filtered}`)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
+
+    return it('Should propogate the error', function(done) {
+      return this.FSPersistorManager.deleteFile(
+        this.location,
+        this.name1,
+        err => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('deleteDirectory', function() {
     beforeEach(function() {
-      return this.Fs.exists.callsArgWith(1,true);
-    });
+      return this.Rimraf.callsArgWith(1, this.error)
+    })
+
+    it('Should call rmdir(rimraf) with correct options', function(done) {
+      return this.FSPersistorManager.deleteDirectory(
+        this.location,
+        this.name1,
+        err => {
+          this.Rimraf.calledWith(
+            `${this.location}/${this.name1Filtered}`
+          ).should.equal(true)
+          return done()
+        }
+      )
+    })
+
+    return it('Should propogate the error', function(done) {
+      return this.FSPersistorManager.deleteDirectory(
+        this.location,
+        this.name1,
+        err => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+  })
 
-    it("Should call exists with correct options", function(done) {
-      return this.FSPersistorManager.checkIfFileExists(this.location, this.name1, exists => {
-        this.Fs.exists.calledWith(`${this.location}/${this.name1Filtered}`).should.equal(true);
-        return done();
-      });
-    });
+  describe('checkIfFileExists', function() {
+    beforeEach(function() {
+      return this.Fs.exists.callsArgWith(1, true)
+    })
+
+    it('Should call exists with correct options', function(done) {
+      return this.FSPersistorManager.checkIfFileExists(
+        this.location,
+        this.name1,
+        exists => {
+          this.Fs.exists
+            .calledWith(`${this.location}/${this.name1Filtered}`)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
 
     // fs.exists simply returns false on any error, so...
-    it("should not return an error", function(done) {
-      return this.FSPersistorManager.checkIfFileExists(this.location, this.name1, (err,exists) => {
-        expect(err).to.be.null;
-        return done();
-      });
-    });
-
-    it("Should return true for existing files", function(done) {
-      this.Fs.exists.callsArgWith(1,true);
-      return this.FSPersistorManager.checkIfFileExists(this.location, this.name1, (err,exists) => {
-        exists.should.be.true;
-        return done();
-      });
-    });
-
-    return it("Should return false for non-existing files", function(done) {
-      this.Fs.exists.callsArgWith(1,false);
-      return this.FSPersistorManager.checkIfFileExists(this.location, this.name1, (err,exists) => {
-        exists.should.be.false;
-        return done();
-      });
-    });
-  });
-
-  return describe("directorySize", function() {
-
-    it("should propogate the error", function(done) {
-      this.Fs.readdir.callsArgWith(1, this.error);
-      return this.FSPersistorManager.directorySize(this.location, this.name1, (err, totalsize) => {
-        err.should.equal(this.error);
-        return done();
-      });
-    });
-
-    return it("should sum directory files size", function(done) {
-      this.Fs.readdir.callsArgWith(1, null, [ {'file1': 'file1'}, {'file2': 'file2'} ]);
-      this.Fs.fstatSync.returns({size : 1024});
-      return this.FSPersistorManager.directorySize(this.location, this.name1, (err, totalsize) => {
-        expect(totalsize).to.equal(2048);
-        return done();
-      });
-    });
-  });
-});
+    it('should not return an error', function(done) {
+      return this.FSPersistorManager.checkIfFileExists(
+        this.location,
+        this.name1,
+        (err, exists) => {
+          expect(err).to.be.null
+          return done()
+        }
+      )
+    })
+
+    it('Should return true for existing files', function(done) {
+      this.Fs.exists.callsArgWith(1, true)
+      return this.FSPersistorManager.checkIfFileExists(
+        this.location,
+        this.name1,
+        (err, exists) => {
+          exists.should.be.true
+          return done()
+        }
+      )
+    })
+
+    return it('Should return false for non-existing files', function(done) {
+      this.Fs.exists.callsArgWith(1, false)
+      return this.FSPersistorManager.checkIfFileExists(
+        this.location,
+        this.name1,
+        (err, exists) => {
+          exists.should.be.false
+          return done()
+        }
+      )
+    })
+  })
+
+  return describe('directorySize', function() {
+    it('should propogate the error', function(done) {
+      this.Fs.readdir.callsArgWith(1, this.error)
+      return this.FSPersistorManager.directorySize(
+        this.location,
+        this.name1,
+        (err, totalsize) => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+
+    return it('should sum directory files size', function(done) {
+      this.Fs.readdir.callsArgWith(1, null, [
+        { file1: 'file1' },
+        { file2: 'file2' }
+      ])
+      this.Fs.fstatSync.returns({ size: 1024 })
+      return this.FSPersistorManager.directorySize(
+        this.location,
+        this.name1,
+        (err, totalsize) => {
+          expect(totalsize).to.equal(2048)
+          return done()
+        }
+      )
+    })
+  })
+})

+ 286 - 278
services/filestore/test/unit/js/FileControllerTests.js

@@ -9,281 +9,289 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/FileController.js";
-const SandboxedModule = require('sandboxed-module');
-
-describe("FileController", function() {
-
-	beforeEach(function() {
-		this.PersistorManager = {
-			sendStream: sinon.stub(),
-			copyFile: sinon.stub(),
-			deleteFile:sinon.stub()
-		};
-
-		this.settings = {
-			s3: {
-				buckets: {
-					user_files:"user_files"
-				}
-			}
-		};
-		this.FileHandler = {
-			getFile: sinon.stub(),
-			getFileSize: sinon.stub(),
-			deleteFile: sinon.stub(),
-			insertFile: sinon.stub(),
-			getDirectorySize: sinon.stub()
-		};
-		this.LocalFileWriter = {};
-		this.controller = SandboxedModule.require(modulePath, { requires: {
-			"./LocalFileWriter":this.LocalFileWriter,
-			"./FileHandler": this.FileHandler,
-			"./PersistorManager":this.PersistorManager,
-			"./Errors": (this.Errors =
-				{NotFoundError: sinon.stub()}),
-			"settings-sharelatex": this.settings,
-			"metrics-sharelatex": { 
-				inc() {}
-			},
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			}
-		}
-	}
-		);
-		this.project_id = "project_id";
-		this.file_id = "file_id";
-		this.bucket = "user_files";
-		this.key = `${this.project_id}/${this.file_id}`;
-		this.req = {
-			key:this.key,
-			bucket:this.bucket,
-			query:{},
-			params: {
-				project_id:this.project_id,
-				file_id:this.file_id
-			},
-			headers: {}
-		};
-		this.res = {
-			set: sinon.stub().returnsThis(),
-			status: sinon.stub().returnsThis()
-		};
-		return this.fileStream = {};});
-
-	describe("getFile", function() {
-
-		it("should pipe the stream", function(done){
-			this.FileHandler.getFile.callsArgWith(3, null, this.fileStream);
-			this.fileStream.pipe = res=> {
-				res.should.equal(this.res);
-				return done();
-			};
-			return this.controller.getFile(this.req, this.res);
-		});
-
-		it("should send a 200 if the cacheWarm param is true", function(done){
-			this.req.query.cacheWarm = true;
-			this.FileHandler.getFile.callsArgWith(3, null, this.fileStream);
-			this.res.send = statusCode=> {
-				statusCode.should.equal(200);
-				return done();
-			};
-			return this.controller.getFile(this.req, this.res);
-		});
-
-		it("should send a 500 if there is a problem", function(done){
-			this.FileHandler.getFile.callsArgWith(3, "error");
-			this.res.send = code=> {
-				code.should.equal(500);
-				return done();
-			};
-			return this.controller.getFile(this.req, this.res);
-		});
-
-		return describe("with a 'Range' header set", function() {
-
-			beforeEach(function() {
-				return this.req.headers.range = 'bytes=0-8';
-			});
-
-			return it("should pass 'start' and 'end' options to FileHandler", function(done) {
-				this.FileHandler.getFile.callsArgWith(3, null, this.fileStream);
-				this.fileStream.pipe = res=> {
-					expect(this.FileHandler.getFile.lastCall.args[2].start).to.equal(0);
-					expect(this.FileHandler.getFile.lastCall.args[2].end).to.equal(8);
-					return done();
-				};
-				return this.controller.getFile(this.req, this.res);
-			});
-		});
-	});
-
-	describe("getFileHead", function() {
-		it("should return the file size in a Content-Length header", function(done) {
-			const expectedFileSize = 84921;
-			this.FileHandler.getFileSize.yields(
-				new Error("FileHandler.getFileSize: unexpected arguments")
-			);
-			this.FileHandler.getFileSize.withArgs(this.bucket, this.key).yields(null, expectedFileSize);
-
-			this.res.end = () => {
-				expect(this.res.status.lastCall.args[0]).to.equal(200);
-				expect(this.res.set.calledWith("Content-Length", expectedFileSize)).to.equal(true);
-				return done();
-			};
-
-			return this.controller.getFileHead(this.req, this.res);
-		});
-
-		it("should return a 404 is the file is not found", function(done) {
-			this.FileHandler.getFileSize.yields(new this.Errors.NotFoundError());
-
-			this.res.end = () => {
-				expect(this.res.status.lastCall.args[0]).to.equal(404);
-				return done();
-			};
-
-			return this.controller.getFileHead(this.req, this.res);
-		});
-
-		return it("should return a 500 on internal errors", function(done) {
-			this.FileHandler.getFileSize.yields(new Error());
-
-			this.res.end = () => {
-				expect(this.res.status.lastCall.args[0]).to.equal(500);
-				return done();
-			};
-
-			return this.controller.getFileHead(this.req, this.res);
-		});
-	});
-
-	describe("insertFile", () => it("should send bucket name key and res to PersistorManager", function(done){
-        this.FileHandler.insertFile.callsArgWith(3);
-        this.res.send = () => {
-            this.FileHandler.insertFile.calledWith(this.bucket, this.key, this.req).should.equal(true);
-            return done();
-        };
-        return this.controller.insertFile(this.req, this.res);
-    }));
-
-	describe("copyFile", function() {
-		beforeEach(function() {
-			this.oldFile_id = "old_file_id";
-			this.oldProject_id = "old_project_id";
-			return this.req.body = {
-				source: {
-					project_id: this.oldProject_id,
-					file_id: this.oldFile_id
-				}
-			};
-		});
-
-		it("should send bucket name and both keys to PersistorManager", function(done){
-			this.PersistorManager.copyFile.callsArgWith(3);
-			this.res.send = code=> {
-				code.should.equal(200);
-				this.PersistorManager.copyFile.calledWith(this.bucket, `${this.oldProject_id}/${this.oldFile_id}`, this.key).should.equal(true);
-				return done();
-			};
-			return this.controller.copyFile(this.req, this.res);
-		});
-
-		it("should send a 404 if the original file was not found", function(done) {
-			this.PersistorManager.copyFile.callsArgWith(3, new this.Errors.NotFoundError());
-			this.res.send = code=> {
-				code.should.equal(404);
-				return done();
-			};
-			return this.controller.copyFile(this.req, this.res);
-		});
-
-		return it("should send a 500 if there was an error", function(done){
-			this.PersistorManager.copyFile.callsArgWith(3, "error");
-			this.res.send = code=> {
-				code.should.equal(500);
-				return done();
-			};
-			return this.controller.copyFile(this.req, this.res);
-		});
-	});
-
-	describe("delete file", function() {
-
-		it("should tell the file handler", function(done){
-			this.FileHandler.deleteFile.callsArgWith(2);
-			this.res.send = code=> {
-				code.should.equal(204);
-				this.FileHandler.deleteFile.calledWith(this.bucket, this.key).should.equal(true);
-				return done();
-			};
-			return this.controller.deleteFile(this.req, this.res);
-		});
-
-		return it("should send a 500 if there was an error", function(done){
-			this.FileHandler.deleteFile.callsArgWith(2, "error");
-			this.res.send = function(code){
-				code.should.equal(500);
-				return done();
-			};
-			return this.controller.deleteFile(this.req, this.res);
-		});
-	});
-
-	describe("_get_range", function() {
-
-		it("should parse a valid Range header", function(done) {
-			const result = this.controller._get_range('bytes=0-200');
-			expect(result).to.not.equal(null);
-			expect(result.start).to.equal(0);
-			expect(result.end).to.equal(200);
-			return done();
-		});
-
-		it("should return null for an invalid Range header", function(done) {
-			const result = this.controller._get_range('wat');
-			expect(result).to.equal(null);
-			return done();
-		});
-
-		return it("should return null for any type other than 'bytes'", function(done) {
-			const result = this.controller._get_range('carrots=0-200');
-			expect(result).to.equal(null);
-			return done();
-		});
-	});
-
-	return describe("directorySize", function() {
-
-		it("should return total directory size bytes", function(done) {
-			this.FileHandler.getDirectorySize.callsArgWith(2, null, 1024);
-			return this.controller.directorySize(this.req, { json:result=> {
-				expect(result['total bytes']).to.equal(1024);
-				return done();
-			}
-		}
-			);
-		});
-
-		return it("should send a 500 if there was an error", function(done){
-			this.FileHandler.getDirectorySize.callsArgWith(2, "error");
-			this.res.send = function(code){
-				code.should.equal(500);
-				return done();
-			};
-			return this.controller.directorySize(this.req, this.res);
-		});
-	});
-});
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/FileController.js'
+const SandboxedModule = require('sandboxed-module')
+
+describe('FileController', function() {
+  beforeEach(function() {
+    this.PersistorManager = {
+      sendStream: sinon.stub(),
+      copyFile: sinon.stub(),
+      deleteFile: sinon.stub()
+    }
+
+    this.settings = {
+      s3: {
+        buckets: {
+          user_files: 'user_files'
+        }
+      }
+    }
+    this.FileHandler = {
+      getFile: sinon.stub(),
+      getFileSize: sinon.stub(),
+      deleteFile: sinon.stub(),
+      insertFile: sinon.stub(),
+      getDirectorySize: sinon.stub()
+    }
+    this.LocalFileWriter = {}
+    this.controller = SandboxedModule.require(modulePath, {
+      requires: {
+        './LocalFileWriter': this.LocalFileWriter,
+        './FileHandler': this.FileHandler,
+        './PersistorManager': this.PersistorManager,
+        './Errors': (this.Errors = { NotFoundError: sinon.stub() }),
+        'settings-sharelatex': this.settings,
+        'metrics-sharelatex': {
+          inc() {}
+        },
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        }
+      }
+    })
+    this.project_id = 'project_id'
+    this.file_id = 'file_id'
+    this.bucket = 'user_files'
+    this.key = `${this.project_id}/${this.file_id}`
+    this.req = {
+      key: this.key,
+      bucket: this.bucket,
+      query: {},
+      params: {
+        project_id: this.project_id,
+        file_id: this.file_id
+      },
+      headers: {}
+    }
+    this.res = {
+      set: sinon.stub().returnsThis(),
+      status: sinon.stub().returnsThis()
+    }
+    return (this.fileStream = {})
+  })
+
+  describe('getFile', function() {
+    it('should pipe the stream', function(done) {
+      this.FileHandler.getFile.callsArgWith(3, null, this.fileStream)
+      this.fileStream.pipe = res => {
+        res.should.equal(this.res)
+        return done()
+      }
+      return this.controller.getFile(this.req, this.res)
+    })
+
+    it('should send a 200 if the cacheWarm param is true', function(done) {
+      this.req.query.cacheWarm = true
+      this.FileHandler.getFile.callsArgWith(3, null, this.fileStream)
+      this.res.send = statusCode => {
+        statusCode.should.equal(200)
+        return done()
+      }
+      return this.controller.getFile(this.req, this.res)
+    })
+
+    it('should send a 500 if there is a problem', function(done) {
+      this.FileHandler.getFile.callsArgWith(3, 'error')
+      this.res.send = code => {
+        code.should.equal(500)
+        return done()
+      }
+      return this.controller.getFile(this.req, this.res)
+    })
+
+    return describe("with a 'Range' header set", function() {
+      beforeEach(function() {
+        return (this.req.headers.range = 'bytes=0-8')
+      })
+
+      return it("should pass 'start' and 'end' options to FileHandler", function(done) {
+        this.FileHandler.getFile.callsArgWith(3, null, this.fileStream)
+        this.fileStream.pipe = res => {
+          expect(this.FileHandler.getFile.lastCall.args[2].start).to.equal(0)
+          expect(this.FileHandler.getFile.lastCall.args[2].end).to.equal(8)
+          return done()
+        }
+        return this.controller.getFile(this.req, this.res)
+      })
+    })
+  })
+
+  describe('getFileHead', function() {
+    it('should return the file size in a Content-Length header', function(done) {
+      const expectedFileSize = 84921
+      this.FileHandler.getFileSize.yields(
+        new Error('FileHandler.getFileSize: unexpected arguments')
+      )
+      this.FileHandler.getFileSize
+        .withArgs(this.bucket, this.key)
+        .yields(null, expectedFileSize)
+
+      this.res.end = () => {
+        expect(this.res.status.lastCall.args[0]).to.equal(200)
+        expect(
+          this.res.set.calledWith('Content-Length', expectedFileSize)
+        ).to.equal(true)
+        return done()
+      }
+
+      return this.controller.getFileHead(this.req, this.res)
+    })
+
+    it('should return a 404 is the file is not found', function(done) {
+      this.FileHandler.getFileSize.yields(new this.Errors.NotFoundError())
+
+      this.res.end = () => {
+        expect(this.res.status.lastCall.args[0]).to.equal(404)
+        return done()
+      }
+
+      return this.controller.getFileHead(this.req, this.res)
+    })
+
+    return it('should return a 500 on internal errors', function(done) {
+      this.FileHandler.getFileSize.yields(new Error())
+
+      this.res.end = () => {
+        expect(this.res.status.lastCall.args[0]).to.equal(500)
+        return done()
+      }
+
+      return this.controller.getFileHead(this.req, this.res)
+    })
+  })
+
+  describe('insertFile', () =>
+    it('should send bucket name key and res to PersistorManager', function(done) {
+      this.FileHandler.insertFile.callsArgWith(3)
+      this.res.send = () => {
+        this.FileHandler.insertFile
+          .calledWith(this.bucket, this.key, this.req)
+          .should.equal(true)
+        return done()
+      }
+      return this.controller.insertFile(this.req, this.res)
+    }))
+
+  describe('copyFile', function() {
+    beforeEach(function() {
+      this.oldFile_id = 'old_file_id'
+      this.oldProject_id = 'old_project_id'
+      return (this.req.body = {
+        source: {
+          project_id: this.oldProject_id,
+          file_id: this.oldFile_id
+        }
+      })
+    })
+
+    it('should send bucket name and both keys to PersistorManager', function(done) {
+      this.PersistorManager.copyFile.callsArgWith(3)
+      this.res.send = code => {
+        code.should.equal(200)
+        this.PersistorManager.copyFile
+          .calledWith(
+            this.bucket,
+            `${this.oldProject_id}/${this.oldFile_id}`,
+            this.key
+          )
+          .should.equal(true)
+        return done()
+      }
+      return this.controller.copyFile(this.req, this.res)
+    })
+
+    it('should send a 404 if the original file was not found', function(done) {
+      this.PersistorManager.copyFile.callsArgWith(
+        3,
+        new this.Errors.NotFoundError()
+      )
+      this.res.send = code => {
+        code.should.equal(404)
+        return done()
+      }
+      return this.controller.copyFile(this.req, this.res)
+    })
+
+    return it('should send a 500 if there was an error', function(done) {
+      this.PersistorManager.copyFile.callsArgWith(3, 'error')
+      this.res.send = code => {
+        code.should.equal(500)
+        return done()
+      }
+      return this.controller.copyFile(this.req, this.res)
+    })
+  })
+
+  describe('delete file', function() {
+    it('should tell the file handler', function(done) {
+      this.FileHandler.deleteFile.callsArgWith(2)
+      this.res.send = code => {
+        code.should.equal(204)
+        this.FileHandler.deleteFile
+          .calledWith(this.bucket, this.key)
+          .should.equal(true)
+        return done()
+      }
+      return this.controller.deleteFile(this.req, this.res)
+    })
+
+    return it('should send a 500 if there was an error', function(done) {
+      this.FileHandler.deleteFile.callsArgWith(2, 'error')
+      this.res.send = function(code) {
+        code.should.equal(500)
+        return done()
+      }
+      return this.controller.deleteFile(this.req, this.res)
+    })
+  })
+
+  describe('_get_range', function() {
+    it('should parse a valid Range header', function(done) {
+      const result = this.controller._get_range('bytes=0-200')
+      expect(result).to.not.equal(null)
+      expect(result.start).to.equal(0)
+      expect(result.end).to.equal(200)
+      return done()
+    })
+
+    it('should return null for an invalid Range header', function(done) {
+      const result = this.controller._get_range('wat')
+      expect(result).to.equal(null)
+      return done()
+    })
+
+    return it("should return null for any type other than 'bytes'", function(done) {
+      const result = this.controller._get_range('carrots=0-200')
+      expect(result).to.equal(null)
+      return done()
+    })
+  })
+
+  return describe('directorySize', function() {
+    it('should return total directory size bytes', function(done) {
+      this.FileHandler.getDirectorySize.callsArgWith(2, null, 1024)
+      return this.controller.directorySize(this.req, {
+        json: result => {
+          expect(result['total bytes']).to.equal(1024)
+          return done()
+        }
+      })
+    })
+
+    return it('should send a 500 if there was an error', function(done) {
+      this.FileHandler.getDirectorySize.callsArgWith(2, 'error')
+      this.res.send = function(code) {
+        code.should.equal(500)
+        return done()
+      }
+      return this.controller.directorySize(this.req, this.res)
+    })
+  })
+})

+ 98 - 98
services/filestore/test/unit/js/FileConverterTests.js

@@ -10,110 +10,110 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/FileConverter.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/FileConverter.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("FileConverter", function() {
+describe('FileConverter', function() {
+  beforeEach(function() {
+    this.safe_exec = sinon.stub()
+    this.converter = SandboxedModule.require(modulePath, {
+      requires: {
+        './SafeExec': this.safe_exec,
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        },
+        'metrics-sharelatex': {
+          inc() {},
+          Timer() {
+            return { done() {} }
+          }
+        },
+        'settings-sharelatex': (this.Settings = {
+          commands: {
+            convertCommandPrefix: []
+          }
+        })
+      }
+    })
 
-	beforeEach(function() {
+    this.sourcePath = '/this/path/here.eps'
+    this.format = 'png'
+    return (this.error = 'Error')
+  })
 
-		this.safe_exec = sinon.stub();
-		this.converter = SandboxedModule.require(modulePath, { requires: {
-			"./SafeExec": this.safe_exec,
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			},
-			"metrics-sharelatex": { 
-				inc() {},
-				Timer() {
-					return {done() {}};
-				}
-			},
-			"settings-sharelatex": (this.Settings = {
-				commands: {
-					convertCommandPrefix: []
-				}
-			})
-		}
-	});
+  describe('convert', function() {
+    it('should convert the source to the requested format', function(done) {
+      this.safe_exec.callsArgWith(2)
+      return this.converter.convert(this.sourcePath, this.format, err => {
+        const args = this.safe_exec.args[0][0]
+        args.indexOf(`${this.sourcePath}[0]`).should.not.equal(-1)
+        args.indexOf(`${this.sourcePath}.${this.format}`).should.not.equal(-1)
+        return done()
+      })
+    })
 
-		this.sourcePath = "/this/path/here.eps";
-		this.format = "png";
-		return this.error = "Error";
-	});
+    it('should return the dest path', function(done) {
+      this.safe_exec.callsArgWith(2)
+      return this.converter.convert(
+        this.sourcePath,
+        this.format,
+        (err, destPath) => {
+          destPath.should.equal(`${this.sourcePath}.${this.format}`)
+          return done()
+        }
+      )
+    })
 
-	describe("convert", function() {
+    it('should return the error from convert', function(done) {
+      this.safe_exec.callsArgWith(2, this.error)
+      return this.converter.convert(this.sourcePath, this.format, err => {
+        err.should.equal(this.error)
+        return done()
+      })
+    })
 
-		it("should convert the source to the requested format", function(done){
-			this.safe_exec.callsArgWith(2);
-			return this.converter.convert(this.sourcePath, this.format, err=> {
-				const args = this.safe_exec.args[0][0];
-				args.indexOf(`${this.sourcePath}[0]`).should.not.equal(-1); 
-				args.indexOf(`${this.sourcePath}.${this.format}`).should.not.equal(-1); 
-				return done();
-			});
-		});
+    it('should not accapt an non aproved format', function(done) {
+      this.safe_exec.callsArgWith(2)
+      return this.converter.convert(this.sourcePath, 'ahhhhh', err => {
+        expect(err).to.exist
+        return done()
+      })
+    })
 
-		it("should return the dest path", function(done){
-			this.safe_exec.callsArgWith(2);
-			return this.converter.convert(this.sourcePath, this.format, (err, destPath)=> {
-				destPath.should.equal(`${this.sourcePath}.${this.format}`);
-				return done();
-			});
-		});
+    return it('should prefix the command with Settings.commands.convertCommandPrefix', function(done) {
+      this.safe_exec.callsArgWith(2)
+      this.Settings.commands.convertCommandPrefix = ['nice']
+      return this.converter.convert(this.sourcePath, this.format, err => {
+        const command = this.safe_exec.args[0][0]
+        command[0].should.equal('nice')
+        return done()
+      })
+    })
+  })
 
-		it("should return the error from convert", function(done){
-			this.safe_exec.callsArgWith(2, this.error);
-			return this.converter.convert(this.sourcePath, this.format, err=> {
-				err.should.equal(this.error);
-				return done();
-			});
-		});
+  describe('thumbnail', () =>
+    it('should call converter resize with args', function(done) {
+      this.safe_exec.callsArgWith(2)
+      return this.converter.thumbnail(this.sourcePath, err => {
+        const args = this.safe_exec.args[0][0]
+        args.indexOf(`${this.sourcePath}[0]`).should.not.equal(-1)
+        return done()
+      })
+    }))
 
-		it("should not accapt an non aproved format", function(done){
-			this.safe_exec.callsArgWith(2);
-			return this.converter.convert(this.sourcePath, "ahhhhh", err=> {
-				expect(err).to.exist;
-				return done();
-			});
-		});
-		
-		return it("should prefix the command with Settings.commands.convertCommandPrefix", function(done) {
-			this.safe_exec.callsArgWith(2);
-			this.Settings.commands.convertCommandPrefix = ["nice"];
-			return this.converter.convert(this.sourcePath, this.format, err=> {
-				const command = this.safe_exec.args[0][0];
-				command[0].should.equal("nice");
-				return done();
-			});
-		});
-	});
-
-	describe("thumbnail", () => it("should call converter resize with args", function(done){
-        this.safe_exec.callsArgWith(2);
-        return this.converter.thumbnail(this.sourcePath, err=> {
-            const args = this.safe_exec.args[0][0];
-            args.indexOf(`${this.sourcePath}[0]`).should.not.equal(-1); 
-            return done();
-        });
-    }));
-
-	return describe("preview", () => it("should call converter resize with args", function(done){
-        this.safe_exec.callsArgWith(2);
-        return this.converter.preview(this.sourcePath, err=> {
-            const args = this.safe_exec.args[0][0];
-            args.indexOf(`${this.sourcePath}[0]`).should.not.equal(-1); 
-            return done();
-        });
-    }));
-});
+  return describe('preview', () =>
+    it('should call converter resize with args', function(done) {
+      this.safe_exec.callsArgWith(2)
+      return this.converter.preview(this.sourcePath, err => {
+        const args = this.safe_exec.args[0][0]
+        args.indexOf(`${this.sourcePath}[0]`).should.not.equal(-1)
+        return done()
+      })
+    }))
+})

+ 332 - 246
services/filestore/test/unit/js/FileHandlerTests.js

@@ -10,273 +10,359 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/FileHandler.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/FileHandler.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("FileHandler", function() {
+describe('FileHandler', function() {
+  beforeEach(function() {
+    this.settings = {
+      s3: {
+        buckets: {
+          user_files: 'user_files'
+        }
+      }
+    }
+    this.PersistorManager = {
+      getFileStream: sinon.stub(),
+      checkIfFileExists: sinon.stub(),
+      deleteFile: sinon.stub(),
+      deleteDirectory: sinon.stub(),
+      sendStream: sinon.stub(),
+      insertFile: sinon.stub(),
+      directorySize: sinon.stub()
+    }
+    this.LocalFileWriter = {
+      writeStream: sinon.stub(),
+      getStream: sinon.stub(),
+      deleteFile: sinon.stub()
+    }
+    this.FileConverter = {
+      convert: sinon.stub(),
+      thumbnail: sinon.stub(),
+      preview: sinon.stub()
+    }
+    this.keyBuilder = {
+      addCachingToKey: sinon.stub(),
+      getConvertedFolderKey: sinon.stub()
+    }
+    this.ImageOptimiser = { compressPng: sinon.stub() }
+    this.handler = SandboxedModule.require(modulePath, {
+      requires: {
+        'settings-sharelatex': this.settings,
+        './PersistorManager': this.PersistorManager,
+        './LocalFileWriter': this.LocalFileWriter,
+        './FileConverter': this.FileConverter,
+        './KeyBuilder': this.keyBuilder,
+        './ImageOptimiser': this.ImageOptimiser,
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        }
+      }
+    })
+    this.bucket = 'my_bucket'
+    this.key = 'key/here'
+    this.stubbedPath = '/var/somewhere/path'
+    this.format = 'png'
+    return (this.formattedStubbedPath = `${this.stubbedPath}.${this.format}`)
+  })
 
-	beforeEach(function() {
-		this.settings = {
-			s3: {
-				buckets: {
-					user_files:"user_files"
-				}
-			}
-		};
-		this.PersistorManager = {
-			getFileStream: sinon.stub(),
-			checkIfFileExists: sinon.stub(),
-			deleteFile: sinon.stub(),
-			deleteDirectory: sinon.stub(),
-			sendStream: sinon.stub(),
-			insertFile: sinon.stub(),
-			directorySize: sinon.stub()
-		};
-		this.LocalFileWriter = {
-			writeStream: sinon.stub(),
-			getStream: sinon.stub(),
-			deleteFile: sinon.stub()
-		};
-		this.FileConverter = {
-			convert: sinon.stub(),
-			thumbnail: sinon.stub(),
-			preview: sinon.stub()
-		};
-		this.keyBuilder = {
-			addCachingToKey: sinon.stub(),
-			getConvertedFolderKey: sinon.stub()
-		};
-		this.ImageOptimiser =
-			{compressPng: sinon.stub()};
-		this.handler = SandboxedModule.require(modulePath, { requires: {
-			"settings-sharelatex": this.settings,
-			"./PersistorManager":this.PersistorManager,
-			"./LocalFileWriter":this.LocalFileWriter,
-			"./FileConverter":this.FileConverter,
-			"./KeyBuilder": this.keyBuilder,
-			"./ImageOptimiser":this.ImageOptimiser,
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			}
-		}
-	}
-		);
-		this.bucket = "my_bucket";
-		this.key = "key/here";
-		this.stubbedPath = "/var/somewhere/path";
-		this.format = "png";
-		return this.formattedStubbedPath = `${this.stubbedPath}.${this.format}`;
-	});
+  describe('insertFile', function() {
+    beforeEach(function() {
+      this.stream = {}
+      this.PersistorManager.deleteDirectory.callsArgWith(2)
+      return this.PersistorManager.sendStream.callsArgWith(3)
+    })
 
-	describe("insertFile", function() {
-		beforeEach(function() {
-			this.stream = {};
-			this.PersistorManager.deleteDirectory.callsArgWith(2);
-			return this.PersistorManager.sendStream.callsArgWith(3);
-		});
+    it('should send file to the filestore', function(done) {
+      return this.handler.insertFile(this.bucket, this.key, this.stream, () => {
+        this.PersistorManager.sendStream
+          .calledWith(this.bucket, this.key, this.stream)
+          .should.equal(true)
+        return done()
+      })
+    })
 
-		it("should send file to the filestore", function(done){
-			return this.handler.insertFile(this.bucket, this.key, this.stream, () => {
-				this.PersistorManager.sendStream.calledWith(this.bucket, this.key, this.stream).should.equal(true);
-				return done();
-			});
-		});
+    return it('should delete the convetedKey folder', function(done) {
+      this.keyBuilder.getConvertedFolderKey.returns(this.stubbedConvetedKey)
+      return this.handler.insertFile(this.bucket, this.key, this.stream, () => {
+        this.PersistorManager.deleteDirectory
+          .calledWith(this.bucket, this.stubbedConvetedKey)
+          .should.equal(true)
+        return done()
+      })
+    })
+  })
 
-		return it("should delete the convetedKey folder", function(done){
-			this.keyBuilder.getConvertedFolderKey.returns(this.stubbedConvetedKey);
-			return this.handler.insertFile(this.bucket, this.key, this.stream, () => {
-				this.PersistorManager.deleteDirectory.calledWith(this.bucket, this.stubbedConvetedKey).should.equal(true);
-				return done();
-			});
-		});
-	});
+  describe('deleteFile', function() {
+    beforeEach(function() {
+      this.keyBuilder.getConvertedFolderKey.returns(this.stubbedConvetedKey)
+      this.PersistorManager.deleteFile.callsArgWith(2)
+      return this.PersistorManager.deleteDirectory.callsArgWith(2)
+    })
 
-	describe("deleteFile", function() {
-		beforeEach(function() {
-			this.keyBuilder.getConvertedFolderKey.returns(this.stubbedConvetedKey);
-			this.PersistorManager.deleteFile.callsArgWith(2);
-			return this.PersistorManager.deleteDirectory.callsArgWith(2);
-		});
+    it('should tell the filestore manager to delete the file', function(done) {
+      return this.handler.deleteFile(this.bucket, this.key, () => {
+        this.PersistorManager.deleteFile
+          .calledWith(this.bucket, this.key)
+          .should.equal(true)
+        return done()
+      })
+    })
 
-		it("should tell the filestore manager to delete the file", function(done){
-			return this.handler.deleteFile(this.bucket, this.key, () => {
-				this.PersistorManager.deleteFile.calledWith(this.bucket, this.key).should.equal(true);
-				return done();
-			});
-		});
+    return it('should tell the filestore manager to delete the cached foler', function(done) {
+      return this.handler.deleteFile(this.bucket, this.key, () => {
+        this.PersistorManager.deleteDirectory
+          .calledWith(this.bucket, this.stubbedConvetedKey)
+          .should.equal(true)
+        return done()
+      })
+    })
+  })
 
-		return it("should tell the filestore manager to delete the cached foler", function(done){
-			return this.handler.deleteFile(this.bucket, this.key, () => {
-				this.PersistorManager.deleteDirectory.calledWith(this.bucket, this.stubbedConvetedKey).should.equal(true);
-				return done();
-			});
-		});
-	});
+  describe('getFile', function() {
+    beforeEach(function() {
+      this.handler._getStandardFile = sinon.stub().callsArgWith(3)
+      return (this.handler._getConvertedFile = sinon.stub().callsArgWith(3))
+    })
 
-	describe("getFile", function() {
-		beforeEach(function() {
-			this.handler._getStandardFile = sinon.stub().callsArgWith(3);
-			return this.handler._getConvertedFile = sinon.stub().callsArgWith(3);
-		});
+    it('should call _getStandardFile if no format or style are defined', function(done) {
+      return this.handler.getFile(this.bucket, this.key, null, () => {
+        this.handler._getStandardFile.called.should.equal(true)
+        this.handler._getConvertedFile.called.should.equal(false)
+        return done()
+      })
+    })
 
-		it("should call _getStandardFile if no format or style are defined", function(done){
+    it('should pass options to _getStandardFile', function(done) {
+      const options = { start: 0, end: 8 }
+      return this.handler.getFile(this.bucket, this.key, options, () => {
+        expect(this.handler._getStandardFile.lastCall.args[2].start).to.equal(0)
+        expect(this.handler._getStandardFile.lastCall.args[2].end).to.equal(8)
+        return done()
+      })
+    })
 
-			return this.handler.getFile(this.bucket, this.key, null, () => {
-				this.handler._getStandardFile.called.should.equal(true);
-				this.handler._getConvertedFile.called.should.equal(false);
-				return done();
-			});
-		});
+    return it('should call _getConvertedFile if a format is defined', function(done) {
+      return this.handler.getFile(
+        this.bucket,
+        this.key,
+        { format: 'png' },
+        () => {
+          this.handler._getStandardFile.called.should.equal(false)
+          this.handler._getConvertedFile.called.should.equal(true)
+          return done()
+        }
+      )
+    })
+  })
 
-		it("should pass options to _getStandardFile", function(done) {
-			const options = {start: 0, end: 8};
-			return this.handler.getFile(this.bucket, this.key, options, () => {
-				expect(this.handler._getStandardFile.lastCall.args[2].start).to.equal(0);
-				expect(this.handler._getStandardFile.lastCall.args[2].end).to.equal(8);
-				return done();
-			});
-		});
+  describe('_getStandardFile', function() {
+    beforeEach(function() {
+      this.fileStream = { on() {} }
+      return this.PersistorManager.getFileStream.callsArgWith(
+        3,
+        'err',
+        this.fileStream
+      )
+    })
 
-		return it("should call _getConvertedFile if a format is defined", function(done){
-			return this.handler.getFile(this.bucket, this.key, {format:"png"}, () => {
-				this.handler._getStandardFile.called.should.equal(false);
-				this.handler._getConvertedFile.called.should.equal(true);
-				return done();
-			});
-		});
-	});
+    it('should get the stream', function(done) {
+      return this.handler.getFile(this.bucket, this.key, null, () => {
+        this.PersistorManager.getFileStream
+          .calledWith(this.bucket, this.key)
+          .should.equal(true)
+        return done()
+      })
+    })
 
-	describe("_getStandardFile", function() {
+    it('should return the stream and error', function(done) {
+      return this.handler.getFile(
+        this.bucket,
+        this.key,
+        null,
+        (err, stream) => {
+          err.should.equal('err')
+          stream.should.equal(this.fileStream)
+          return done()
+        }
+      )
+    })
 
-		beforeEach(function() {
-			this.fileStream = {on() {}};
-			return this.PersistorManager.getFileStream.callsArgWith(3, "err", this.fileStream);
-		});
+    return it('should pass options to PersistorManager', function(done) {
+      return this.handler.getFile(
+        this.bucket,
+        this.key,
+        { start: 0, end: 8 },
+        () => {
+          expect(
+            this.PersistorManager.getFileStream.lastCall.args[2].start
+          ).to.equal(0)
+          expect(
+            this.PersistorManager.getFileStream.lastCall.args[2].end
+          ).to.equal(8)
+          return done()
+        }
+      )
+    })
+  })
 
-		it("should get the stream", function(done){
-			return this.handler.getFile(this.bucket, this.key, null, () => {
-				this.PersistorManager.getFileStream.calledWith(this.bucket, this.key).should.equal(true);
-				return done();
-			});
-		});
+  describe('_getConvertedFile', function() {
+    it('should getFileStream if it does exists', function(done) {
+      this.PersistorManager.checkIfFileExists.callsArgWith(2, null, true)
+      this.PersistorManager.getFileStream.callsArgWith(3)
+      return this.handler._getConvertedFile(this.bucket, this.key, {}, () => {
+        this.PersistorManager.getFileStream
+          .calledWith(this.bucket)
+          .should.equal(true)
+        return done()
+      })
+    })
 
-		it("should return the stream and error", function(done){
-			return this.handler.getFile(this.bucket, this.key, null, (err, stream)=> {
-				err.should.equal("err");
-				stream.should.equal(this.fileStream);
-				return done();
-			});
-		});
+    return it('should call _getConvertedFileAndCache if it does exists', function(done) {
+      this.PersistorManager.checkIfFileExists.callsArgWith(2, null, false)
+      this.handler._getConvertedFileAndCache = sinon.stub().callsArgWith(4)
+      return this.handler._getConvertedFile(this.bucket, this.key, {}, () => {
+        this.handler._getConvertedFileAndCache
+          .calledWith(this.bucket, this.key)
+          .should.equal(true)
+        return done()
+      })
+    })
+  })
 
-		return it("should pass options to PersistorManager", function(done) {
-			return this.handler.getFile(this.bucket, this.key, {start: 0, end: 8}, () => {
-				expect(this.PersistorManager.getFileStream.lastCall.args[2].start).to.equal(0);
-				expect(this.PersistorManager.getFileStream.lastCall.args[2].end).to.equal(8);
-				return done();
-			});
-		});
-	});
+  describe('_getConvertedFileAndCache', () =>
+    it('should _convertFile ', function(done) {
+      this.stubbedStream = { something: 'here' }
+      this.localStream = {
+        on() {}
+      }
+      this.PersistorManager.sendFile = sinon.stub().callsArgWith(3)
+      this.LocalFileWriter.getStream = sinon
+        .stub()
+        .callsArgWith(1, null, this.localStream)
+      this.convetedKey = this.key + 'converted'
+      this.handler._convertFile = sinon
+        .stub()
+        .callsArgWith(3, null, this.stubbedPath)
+      this.ImageOptimiser.compressPng = sinon.stub().callsArgWith(1)
+      return this.handler._getConvertedFileAndCache(
+        this.bucket,
+        this.key,
+        this.convetedKey,
+        {},
+        (err, fsStream) => {
+          this.handler._convertFile.called.should.equal(true)
+          this.PersistorManager.sendFile
+            .calledWith(this.bucket, this.convetedKey, this.stubbedPath)
+            .should.equal(true)
+          this.ImageOptimiser.compressPng
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          this.LocalFileWriter.getStream
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          fsStream.should.equal(this.localStream)
+          return done()
+        }
+      )
+    }))
 
+  describe('_convertFile', function() {
+    beforeEach(function() {
+      this.FileConverter.convert.callsArgWith(
+        2,
+        null,
+        this.formattedStubbedPath
+      )
+      this.FileConverter.thumbnail.callsArgWith(
+        1,
+        null,
+        this.formattedStubbedPath
+      )
+      this.FileConverter.preview.callsArgWith(
+        1,
+        null,
+        this.formattedStubbedPath
+      )
+      this.handler._writeS3FileToDisk = sinon
+        .stub()
+        .callsArgWith(3, null, this.stubbedPath)
+      return this.LocalFileWriter.deleteFile.callsArgWith(1)
+    })
 
-	describe("_getConvertedFile", function() {
+    it('should call thumbnail on the writer path if style was thumbnail was specified', function(done) {
+      return this.handler._convertFile(
+        this.bucket,
+        this.key,
+        { style: 'thumbnail' },
+        (err, path) => {
+          path.should.equal(this.formattedStubbedPath)
+          this.FileConverter.thumbnail
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          this.LocalFileWriter.deleteFile
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
 
-		it("should getFileStream if it does exists", function(done){
-			this.PersistorManager.checkIfFileExists.callsArgWith(2, null, true);
-			this.PersistorManager.getFileStream.callsArgWith(3);
-			return this.handler._getConvertedFile(this.bucket, this.key, {}, () => {
-				this.PersistorManager.getFileStream.calledWith(this.bucket).should.equal(true);
-				return done();
-			});
-		});
+    it('should call preview on the writer path if style was preview was specified', function(done) {
+      return this.handler._convertFile(
+        this.bucket,
+        this.key,
+        { style: 'preview' },
+        (err, path) => {
+          path.should.equal(this.formattedStubbedPath)
+          this.FileConverter.preview
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          this.LocalFileWriter.deleteFile
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
 
-		return it("should call _getConvertedFileAndCache if it does exists", function(done){
-			this.PersistorManager.checkIfFileExists.callsArgWith(2, null, false);
-			this.handler._getConvertedFileAndCache = sinon.stub().callsArgWith(4);
-			return this.handler._getConvertedFile(this.bucket, this.key, {}, () => {
-				this.handler._getConvertedFileAndCache.calledWith(this.bucket, this.key).should.equal(true);
-				return done();
-			});
-		});
-	});
+    return it('should call convert on the writer path if a format was specified', function(done) {
+      return this.handler._convertFile(
+        this.bucket,
+        this.key,
+        { format: this.format },
+        (err, path) => {
+          path.should.equal(this.formattedStubbedPath)
+          this.FileConverter.convert
+            .calledWith(this.stubbedPath, this.format)
+            .should.equal(true)
+          this.LocalFileWriter.deleteFile
+            .calledWith(this.stubbedPath)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
+  })
 
-	describe("_getConvertedFileAndCache", () => it("should _convertFile ", function(done){
-        this.stubbedStream = {"something":"here"};
-        this.localStream = {
-            on() {}
-        };
-        this.PersistorManager.sendFile = sinon.stub().callsArgWith(3);
-        this.LocalFileWriter.getStream = sinon.stub().callsArgWith(1, null, this.localStream);
-        this.convetedKey = this.key+"converted";
-        this.handler._convertFile = sinon.stub().callsArgWith(3, null, this.stubbedPath);
-        this.ImageOptimiser.compressPng = sinon.stub().callsArgWith(1);
-        return this.handler._getConvertedFileAndCache(this.bucket, this.key, this.convetedKey, {}, (err, fsStream)=> {
-            this.handler._convertFile.called.should.equal(true);
-            this.PersistorManager.sendFile.calledWith(this.bucket, this.convetedKey, this.stubbedPath).should.equal(true);
-            this.ImageOptimiser.compressPng.calledWith(this.stubbedPath).should.equal(true);
-            this.LocalFileWriter.getStream.calledWith(this.stubbedPath).should.equal(true);
-            fsStream.should.equal(this.localStream);
-            return done();
-        });
-    }));
+  return describe('getDirectorySize', function() {
+    beforeEach(function() {
+      return this.PersistorManager.directorySize.callsArgWith(2)
+    })
 
-	describe("_convertFile", function() {
-		beforeEach(function() {
-			this.FileConverter.convert.callsArgWith(2, null, this.formattedStubbedPath);
-			this.FileConverter.thumbnail.callsArgWith(1, null, this.formattedStubbedPath);
-			this.FileConverter.preview.callsArgWith(1, null, this.formattedStubbedPath);
-			this.handler._writeS3FileToDisk = sinon.stub().callsArgWith(3, null, this.stubbedPath);
-			return this.LocalFileWriter.deleteFile.callsArgWith(1);
-		});
-
-		it("should call thumbnail on the writer path if style was thumbnail was specified", function(done){
-			return this.handler._convertFile(this.bucket, this.key, {style:"thumbnail"}, (err, path)=> {
-				path.should.equal(this.formattedStubbedPath);
-				this.FileConverter.thumbnail.calledWith(this.stubbedPath).should.equal(true);
-				this.LocalFileWriter.deleteFile.calledWith(this.stubbedPath).should.equal(true);
-				return done();
-			});
-		});
-
-		it("should call preview on the writer path if style was preview was specified", function(done){
-			return this.handler._convertFile(this.bucket, this.key, {style:"preview"}, (err, path)=> {
-				path.should.equal(this.formattedStubbedPath);
-				this.FileConverter.preview.calledWith(this.stubbedPath).should.equal(true);
-				this.LocalFileWriter.deleteFile.calledWith(this.stubbedPath).should.equal(true);
-				return done();
-			});
-		});
-
-		return it("should call convert on the writer path if a format was specified", function(done){
-			return this.handler._convertFile(this.bucket, this.key, {format:this.format}, (err, path)=> {
-				path.should.equal(this.formattedStubbedPath);
-				this.FileConverter.convert.calledWith(this.stubbedPath, this.format).should.equal(true);
-				this.LocalFileWriter.deleteFile.calledWith(this.stubbedPath).should.equal(true);
-				return done();
-			});
-		});
-	});
-
-	return describe("getDirectorySize", function() {
-
-		beforeEach(function() {
-			return this.PersistorManager.directorySize.callsArgWith(2);
-		});
-
-		return it("should call the filestore manager to get directory size", function(done){
-			return this.handler.getDirectorySize(this.bucket, this.key, () => {
-				this.PersistorManager.directorySize.calledWith(this.bucket, this.key).should.equal(true);
-				return done();
-			});
-		});
-	});
-});
+    return it('should call the filestore manager to get directory size', function(done) {
+      return this.handler.getDirectorySize(this.bucket, this.key, () => {
+        this.PersistorManager.directorySize
+          .calledWith(this.bucket, this.key)
+          .should.equal(true)
+        return done()
+      })
+    })
+  })
+})

+ 63 - 73
services/filestore/test/unit/js/ImageOptimiserTests.js

@@ -10,82 +10,72 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/ImageOptimiser.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/ImageOptimiser.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("ImageOptimiser", function() {
+describe('ImageOptimiser', function() {
+  beforeEach(function() {
+    this.child_process = { exec: sinon.stub() }
+    this.settings = { enableConversions: true }
+    this.optimiser = SandboxedModule.require(modulePath, {
+      requires: {
+        child_process: this.child_process,
+        'logger-sharelatex': {
+          log() {},
+          err() {},
+          warn() {}
+        },
+        'settings-sharelatex': this.settings
+      }
+    })
 
-	beforeEach(function() {
-		this.child_process =
-			{exec : sinon.stub()};
-		this.settings = 
-			{enableConversions:true};
-		this.optimiser = SandboxedModule.require(modulePath, { requires: {
-			'child_process': this.child_process,
-			"logger-sharelatex": {
-				log() {},
-				err() {},
-				warn() {}
-			},
-			"settings-sharelatex": this.settings
-		}
-	}
-		);
-				
+    this.sourcePath = '/this/path/here.eps'
+    return (this.error = 'Error')
+  })
 
-		this.sourcePath = "/this/path/here.eps";
-		return this.error = "Error";
-	});
+  describe('compressPng', function() {
+    it('convert the file', function(done) {
+      this.child_process.exec.callsArgWith(2)
+      return this.optimiser.compressPng(this.sourcePath, err => {
+        const args = this.child_process.exec.args[0][0]
+        args.should.equal(`optipng ${this.sourcePath}`)
+        return done()
+      })
+    })
 
-	describe("compressPng", function() {
-		
+    return it('should return the error', function(done) {
+      this.child_process.exec.callsArgWith(2, this.error)
+      return this.optimiser.compressPng(this.sourcePath, err => {
+        err.should.equal(this.error)
+        return done()
+      })
+    })
+  })
 
-		it("convert the file", function(done){
-			this.child_process.exec.callsArgWith(2);
-			return this.optimiser.compressPng(this.sourcePath, err=> {
-				const args = this.child_process.exec.args[0][0];
-				args.should.equal(`optipng ${this.sourcePath}`);
-				return done();
-			});
-		});
+  describe('when enableConversions is disabled', () =>
+    it('should produce an error', function(done) {
+      this.settings.enableConversions = false
+      this.child_process.exec.callsArgWith(2)
+      return this.optimiser.compressPng(this.sourcePath, err => {
+        this.child_process.exec.called.should.equal(false)
+        expect(err).to.exist
+        return done()
+      })
+    }))
 
-
-		return it("should return the error", function(done){
-			this.child_process.exec.callsArgWith(2, this.error);
-			return this.optimiser.compressPng(this.sourcePath, err=> {
-				err.should.equal(this.error);
-				return done();
-			});
-		});
-	});
-
-	describe('when enableConversions is disabled', () => it('should produce an error', function(done) {
-        this.settings.enableConversions = false;
-        this.child_process.exec.callsArgWith(2);
-        return this.optimiser.compressPng(this.sourcePath, err=> {
-            this.child_process.exec.called.should.equal(false);
-            expect(err).to.exist;
-            return done();
-        });
-    }));
-
-
-	return describe('when optimiser is sigkilled', () => it('should not produce an error', function(done) {
-        this.error = new Error('woops');
-        this.error.signal = 'SIGKILL';
-        this.child_process.exec.callsArgWith(2, this.error);
-        return this.optimiser.compressPng(this.sourcePath, err=> {
-            expect(err).to.equal(null);
-            return done();
-        });
-    }));
-});
+  return describe('when optimiser is sigkilled', () =>
+    it('should not produce an error', function(done) {
+      this.error = new Error('woops')
+      this.error.signal = 'SIGKILL'
+      this.child_process.exec.callsArgWith(2, this.error)
+      return this.optimiser.compressPng(this.sourcePath, err => {
+        expect(err).to.equal(null)
+        return done()
+      })
+    }))
+})

+ 42 - 49
services/filestore/test/unit/js/KeybuilderTests.js

@@ -10,56 +10,49 @@
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
 
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/KeyBuilder.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/KeyBuilder.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("LocalFileWriter", function() {
+describe('LocalFileWriter', function() {
+  beforeEach(function() {
+    this.keyBuilder = SandboxedModule.require(modulePath, {
+      requires: {
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        }
+      }
+    })
+    return (this.key = '123/456')
+  })
 
-	beforeEach(function() {
+  return describe('cachedKey', function() {
+    it('should add the fomat on', function() {
+      const opts = { format: 'png' }
+      const newKey = this.keyBuilder.addCachingToKey(this.key, opts)
+      return newKey.should.equal(`${this.key}-converted-cache/format-png`)
+    })
 
-		this.keyBuilder = SandboxedModule.require(modulePath, { requires: {
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			}
-		}
-	}
-		);
-		return this.key = "123/456";
-	});
-		
-	return describe("cachedKey", function() {
+    it('should add the style on', function() {
+      const opts = { style: 'thumbnail' }
+      const newKey = this.keyBuilder.addCachingToKey(this.key, opts)
+      return newKey.should.equal(`${this.key}-converted-cache/style-thumbnail`)
+    })
 
-		it("should add the fomat on", function() {
-			const opts =
-				{format: "png"};
-			const newKey = this.keyBuilder.addCachingToKey(this.key, opts);
-			return newKey.should.equal(`${this.key}-converted-cache/format-png`);
-		});
-
-		it("should add the style on", function() {
-			const opts =
-				{style: "thumbnail"};
-			const newKey = this.keyBuilder.addCachingToKey(this.key, opts);
-			return newKey.should.equal(`${this.key}-converted-cache/style-thumbnail`);
-		});
-
-		return it("should add format on first", function() {
-			const opts = {
-				style: "thumbnail",
-				format: "png"
-			};
-			const newKey = this.keyBuilder.addCachingToKey(this.key, opts);
-			return newKey.should.equal(`${this.key}-converted-cache/format-png-style-thumbnail`);
-		});
-	});
-});
+    return it('should add format on first', function() {
+      const opts = {
+        style: 'thumbnail',
+        format: 'png'
+      }
+      const newKey = this.keyBuilder.addCachingToKey(this.key, opts)
+      return newKey.should.equal(
+        `${this.key}-converted-cache/format-png-style-thumbnail`
+      )
+    })
+  })
+})

+ 101 - 107
services/filestore/test/unit/js/LocalFileWriterTests.js

@@ -11,118 +11,112 @@
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
 
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/LocalFileWriter.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/LocalFileWriter.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("LocalFileWriter", function() {
+describe('LocalFileWriter', function() {
+  beforeEach(function() {
+    this.writeStream = {
+      on(type, cb) {
+        if (type === 'finish') {
+          return cb()
+        }
+      }
+    }
+    this.readStream = { on() {} }
+    this.fs = {
+      createWriteStream: sinon.stub().returns(this.writeStream),
+      createReadStream: sinon.stub().returns(this.readStream),
+      unlink: sinon.stub()
+    }
+    this.settings = {
+      path: {
+        uploadFolder: 'somewhere'
+      }
+    }
+    this.writer = SandboxedModule.require(modulePath, {
+      requires: {
+        fs: this.fs,
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        },
+        'settings-sharelatex': this.settings,
+        'metrics-sharelatex': {
+          inc() {},
+          Timer() {
+            return { done() {} }
+          }
+        }
+      }
+    })
 
-	beforeEach(function() {
+    return (this.stubbedFsPath = 'something/uploads/eio2k1j3')
+  })
 
-		this.writeStream = { 
-			on(type, cb){
-				if (type === "finish") {
-					return cb();
-				}
-			}
-		};
-		this.readStream = 
-			{on() {}};
-		this.fs = { 
-			createWriteStream : sinon.stub().returns(this.writeStream),
-			createReadStream: sinon.stub().returns(this.readStream),
-			unlink: sinon.stub()
-		};
-		this.settings = {
-			path: {
-				uploadFolder:"somewhere"
-			}
-		};
-		this.writer = SandboxedModule.require(modulePath, { requires: {
-			"fs": this.fs,
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			},
-			"settings-sharelatex":this.settings,
-			"metrics-sharelatex": { 
-				inc() {},
-				Timer() {
-					return {done() {}};
-				}
-			}
-		}
-	}
-		);
+  describe('writeStrem', function() {
+    beforeEach(function() {
+      return (this.writer._getPath = sinon.stub().returns(this.stubbedFsPath))
+    })
 
-		return this.stubbedFsPath = "something/uploads/eio2k1j3";
-	});
+    it('write the stream to ./uploads', function(done) {
+      const stream = {
+        pipe: dest => {
+          dest.should.equal(this.writeStream)
+          return done()
+        },
+        on() {}
+      }
+      return this.writer.writeStream(stream, null, () => {})
+    })
 
-	describe("writeStrem", function() {
-		beforeEach(function() {
-			return this.writer._getPath = sinon.stub().returns(this.stubbedFsPath);
-		});
+    return it('should send the path in the callback', function(done) {
+      const stream = {
+        pipe: dest => {},
+        on(type, cb) {
+          if (type === 'end') {
+            return cb()
+          }
+        }
+      }
+      return this.writer.writeStream(stream, null, (err, fsPath) => {
+        fsPath.should.equal(this.stubbedFsPath)
+        return done()
+      })
+    })
+  })
 
-		it("write the stream to ./uploads", function(done){
-			const stream = { 
-				pipe: dest=> {
-					dest.should.equal(this.writeStream);
-					return done();
-				},
-				on() {}
-			};
-			return this.writer.writeStream(stream, null, ()=> {});
-		});
+  describe('getStream', function() {
+    it('should read the stream from the file ', function(done) {
+      return this.writer.getStream(this.stubbedFsPath, (err, stream) => {
+        this.fs.createReadStream
+          .calledWith(this.stubbedFsPath)
+          .should.equal(true)
+        return done()
+      })
+    })
 
-		return it("should send the path in the callback", function(done){
-			const stream = { 
-				pipe: dest=> {},
-				on(type, cb){
-					if (type === "end") {
-						return cb();
-					}
-				}
-			};
-			return this.writer.writeStream(stream, null, (err, fsPath)=> {
-				fsPath.should.equal(this.stubbedFsPath);
-				return done();
-			});
-		});
-	});
-
-	describe("getStream", function() {
-
-		it("should read the stream from the file ", function(done){
-			return this.writer.getStream(this.stubbedFsPath, (err, stream)=> {
-				this.fs.createReadStream.calledWith(this.stubbedFsPath).should.equal(true);
-				return done();
-			});
-		});
-
-		return it("should send the stream in the callback", function(done){
-			return this.writer.getStream(this.stubbedFsPath, (err, readStream)=> {
-				readStream.should.equal(this.readStream);
-				return done();
-			});
-		});
-	});
-
-	return describe("delete file", () => it("should unlink the file", function(done){
-        const error = "my error";
-        this.fs.unlink.callsArgWith(1, error);
-        return this.writer.deleteFile(this.stubbedFsPath, err=> {
-            this.fs.unlink.calledWith(this.stubbedFsPath).should.equal(true);
-            err.should.equal(error);
-            return done();
-        });
-    }));
-});
+    return it('should send the stream in the callback', function(done) {
+      return this.writer.getStream(this.stubbedFsPath, (err, readStream) => {
+        readStream.should.equal(this.readStream)
+        return done()
+      })
+    })
+  })
 
+  return describe('delete file', () =>
+    it('should unlink the file', function(done) {
+      const error = 'my error'
+      this.fs.unlink.callsArgWith(1, error)
+      return this.writer.deleteFile(this.stubbedFsPath, err => {
+        this.fs.unlink.calledWith(this.stubbedFsPath).should.equal(true)
+        err.should.equal(error)
+        return done()
+      })
+    }))
+})

+ 115 - 115
services/filestore/test/unit/js/PersistorManagerTests.js

@@ -9,129 +9,129 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const logger = require("logger-sharelatex");
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/PersistorManager.js";
-const SandboxedModule = require('sandboxed-module');
+const logger = require('logger-sharelatex')
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/PersistorManager.js'
+const SandboxedModule = require('sandboxed-module')
 
+describe('PersistorManagerTests', function() {
+  beforeEach(function() {
+    return (this.S3PersistorManager = {
+      getFileStream: sinon.stub(),
+      checkIfFileExists: sinon.stub(),
+      deleteFile: sinon.stub(),
+      deleteDirectory: sinon.stub(),
+      sendStream: sinon.stub(),
+      insertFile: sinon.stub()
+    })
+  })
 
-describe("PersistorManagerTests", function() {
-
-	beforeEach(function() {
-		return this.S3PersistorManager = {
-			getFileStream: sinon.stub(),
-			checkIfFileExists: sinon.stub(),
-			deleteFile: sinon.stub(),
-			deleteDirectory: sinon.stub(),
-			sendStream: sinon.stub(),
-			insertFile: sinon.stub()
-		};
-	});
-
-	describe("test s3 mixin", function() {
-		beforeEach(function() {
-			this.settings = {
-				filestore: {
-					backend: "s3"
-				}
-			};
-			this.requires = {
-				"./S3PersistorManager": this.S3PersistorManager,
-				"settings-sharelatex": this.settings,
-				"logger-sharelatex": {
-					log() {},
-					err() {}
-				}
-			};
-			return this.PersistorManager = SandboxedModule.require(modulePath, {requires: this.requires});
-		});
-
-		it("should load getFileStream", function(done) {
-			this.PersistorManager.should.respondTo("getFileStream");
-			this.PersistorManager.getFileStream();
-			this.S3PersistorManager.getFileStream.calledOnce.should.equal(true);
-			return done();
-		});
+  describe('test s3 mixin', function() {
+    beforeEach(function() {
+      this.settings = {
+        filestore: {
+          backend: 's3'
+        }
+      }
+      this.requires = {
+        './S3PersistorManager': this.S3PersistorManager,
+        'settings-sharelatex': this.settings,
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        }
+      }
+      return (this.PersistorManager = SandboxedModule.require(modulePath, {
+        requires: this.requires
+      }))
+    })
 
-		it("should load checkIfFileExists", function(done) {
-			this.PersistorManager.checkIfFileExists();
-			this.S3PersistorManager.checkIfFileExists.calledOnce.should.equal(true);
-			return done();
-		});
+    it('should load getFileStream', function(done) {
+      this.PersistorManager.should.respondTo('getFileStream')
+      this.PersistorManager.getFileStream()
+      this.S3PersistorManager.getFileStream.calledOnce.should.equal(true)
+      return done()
+    })
 
-		it("should load deleteFile", function(done) {
-			this.PersistorManager.deleteFile();
-			this.S3PersistorManager.deleteFile.calledOnce.should.equal(true);
-			return done();
-		});
+    it('should load checkIfFileExists', function(done) {
+      this.PersistorManager.checkIfFileExists()
+      this.S3PersistorManager.checkIfFileExists.calledOnce.should.equal(true)
+      return done()
+    })
 
-		it("should load deleteDirectory", function(done) {
-			this.PersistorManager.deleteDirectory();
-			this.S3PersistorManager.deleteDirectory.calledOnce.should.equal(true);
-			return done();
-		});
+    it('should load deleteFile', function(done) {
+      this.PersistorManager.deleteFile()
+      this.S3PersistorManager.deleteFile.calledOnce.should.equal(true)
+      return done()
+    })
 
-		it("should load sendStream", function(done) {
-			this.PersistorManager.sendStream();
-			this.S3PersistorManager.sendStream.calledOnce.should.equal(true);
-			return done();
-		});
+    it('should load deleteDirectory', function(done) {
+      this.PersistorManager.deleteDirectory()
+      this.S3PersistorManager.deleteDirectory.calledOnce.should.equal(true)
+      return done()
+    })
 
-		return it("should load insertFile", function(done) {
-			this.PersistorManager.insertFile();
-			this.S3PersistorManager.insertFile.calledOnce.should.equal(true);
-			return done();
-		});
-	});
+    it('should load sendStream', function(done) {
+      this.PersistorManager.sendStream()
+      this.S3PersistorManager.sendStream.calledOnce.should.equal(true)
+      return done()
+    })
 
-	describe("test unspecified mixins", () => it("should load s3 when no wrapper specified", function(done) {
-        this.settings = {filestore:{}};
-        this.requires = {
-            "./S3PersistorManager": this.S3PersistorManager,
-            "settings-sharelatex": this.settings,
-            "logger-sharelatex": {
-                log() {},
-                err() {}
-            }
-        };
-        this.PersistorManager = SandboxedModule.require(modulePath, {requires: this.requires});
-        this.PersistorManager.should.respondTo("getFileStream");
-        this.PersistorManager.getFileStream();
-        this.S3PersistorManager.getFileStream.calledOnce.should.equal(true);
-        return done();
-    }));
+    return it('should load insertFile', function(done) {
+      this.PersistorManager.insertFile()
+      this.S3PersistorManager.insertFile.calledOnce.should.equal(true)
+      return done()
+    })
+  })
 
-	return describe("test invalid mixins", () => it("should not load an invalid wrapper", function(done) {
-        this.settings = {
-            filestore: {
-                backend:"magic"
-            }
-        };
-        this.requires = {
-            "./S3PersistorManager": this.S3PersistorManager,
-            "settings-sharelatex": this.settings,
-            "logger-sharelatex": {
-                log() {},
-                err() {}
-            }
-        };
-        this.fsWrapper=null;
-        try {
-            this.PersistorManager=SandboxedModule.require(modulePath, {requires: this.requires});
-        } catch (error) {
-            assert.equal("Unknown filestore backend: magic",error.message);
+  describe('test unspecified mixins', () =>
+    it('should load s3 when no wrapper specified', function(done) {
+      this.settings = { filestore: {} }
+      this.requires = {
+        './S3PersistorManager': this.S3PersistorManager,
+        'settings-sharelatex': this.settings,
+        'logger-sharelatex': {
+          log() {},
+          err() {}
         }
-        assert.isNull(this.fsWrapper);
-        return done();
-    }));
-});
-
+      }
+      this.PersistorManager = SandboxedModule.require(modulePath, {
+        requires: this.requires
+      })
+      this.PersistorManager.should.respondTo('getFileStream')
+      this.PersistorManager.getFileStream()
+      this.S3PersistorManager.getFileStream.calledOnce.should.equal(true)
+      return done()
+    }))
 
+  return describe('test invalid mixins', () =>
+    it('should not load an invalid wrapper', function(done) {
+      this.settings = {
+        filestore: {
+          backend: 'magic'
+        }
+      }
+      this.requires = {
+        './S3PersistorManager': this.S3PersistorManager,
+        'settings-sharelatex': this.settings,
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        }
+      }
+      this.fsWrapper = null
+      try {
+        this.PersistorManager = SandboxedModule.require(modulePath, {
+          requires: this.requires
+        })
+      } catch (error) {
+        assert.equal('Unknown filestore backend: magic', error.message)
+      }
+      assert.isNull(this.fsWrapper)
+      return done()
+    }))
+})

+ 606 - 441
services/filestore/test/unit/js/S3PersistorManagerTests.js

@@ -11,444 +11,609 @@
  * DS207: Consider shorter variations of null checks
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/S3PersistorManager.js";
-const SandboxedModule = require('sandboxed-module');
-
-describe("S3PersistorManagerTests", function() {
-
-	beforeEach(function() {
-		this.settings = {
-			filestore: {
-				backend: "s3",
-				s3: {
-					secret: "secret",
-					key: "this_key"
-				},
-				stores: {
-					user_files:"sl_user_files"
-				}
-			}
-		};
-		this.knoxClient = {
-			putFile:sinon.stub(),
-			copyFile:sinon.stub(),
-			list: sinon.stub(),
-			deleteMultiple: sinon.stub(),
-			get: sinon.stub()
-		};
-		this.knox =
-			{createClient: sinon.stub().returns(this.knoxClient)};
-		this.s3EventHandlers = {};
-		this.s3Request = {
-			on: sinon.stub().callsFake((event, callback) => {
-				return this.s3EventHandlers[event] = callback;
-			}),
-			send: sinon.stub()
-		};
-		this.s3Response = {
-			httpResponse: {
-				createUnbufferedStream: sinon.stub()
-			}
-		};
-		this.s3Client = {
-			copyObject: sinon.stub(),
-			headObject: sinon.stub(),
-			getObject: sinon.stub().returns(this.s3Request)
-		};
-		this.awsS3 = sinon.stub().returns(this.s3Client);
-		this.LocalFileWriter = {
-			writeStream: sinon.stub(),
-			deleteFile: sinon.stub()
-		};
-		this.request = sinon.stub();
-		this.requires = {
-			"knox": this.knox,
-			"aws-sdk/clients/s3": this.awsS3,
-			"settings-sharelatex": this.settings,
-			"./LocalFileWriter":this.LocalFileWriter,
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			},
-			"request": this.request,
-			"./Errors": (this.Errors =
-				{NotFoundError: sinon.stub()})
-		};
-		this.key = "my/key";
-		this.bucketName = "my-bucket";
-		this.error = "my errror";
-		return this.S3PersistorManager = SandboxedModule.require(modulePath, {requires: this.requires});
-	});
-
-	describe("getFileStream", function() {
-		describe("success", function() {
-			beforeEach(function() {
-				this.expectedStream = { expectedStream: true };
-				this.expectedStream.on = sinon.stub();
-				this.s3Request.send.callsFake(() => {
-					return this.s3EventHandlers.httpHeaders(200, {}, this.s3Response, "OK");
-				});
-				return this.s3Response.httpResponse.createUnbufferedStream.returns(this.expectedStream);
-			});
-
-			it("returns a stream", function(done) {
-				return this.S3PersistorManager.getFileStream(this.bucketName, this.key, {}, (err, stream) => {
-					if (err != null) {
-						return done(err);
-					}
-					expect(stream).to.equal(this.expectedStream);
-					return done();
-				});
-			});
-
-			it("sets the AWS client up with credentials from settings", function(done) {
-				return this.S3PersistorManager.getFileStream(this.bucketName, this.key, {}, (err, stream) => {
-					if (err != null) {
-						return done(err);
-					}
-					expect(this.awsS3.lastCall.args).to.deep.equal([{
-						credentials: {
-							accessKeyId: this.settings.filestore.s3.key,
-							secretAccessKey: this.settings.filestore.s3.secret
-						}
-					}]);
-					return done();
-				});
-			});
-
-			it("fetches the right key from the right bucket", function(done) {
-				return this.S3PersistorManager.getFileStream(this.bucketName, this.key, {}, (err, stream) => {
-					if (err != null) {
-						return done(err);
-					}
-					expect(this.s3Client.getObject.lastCall.args).to.deep.equal([{
-						Bucket: this.bucketName,
-						Key: this.key
-					}]);
-					return done();
-				});
-			});
-
-			it("accepts alternative credentials", function(done) {
-				const accessKeyId = "that_key";
-				const secret = "that_secret";
-				const opts = {
-					credentials: {
-						auth_key: accessKeyId,
-						auth_secret: secret
-					}
-				};
-				return this.S3PersistorManager.getFileStream(this.bucketName, this.key, opts, (err, stream) => {
-					if (err != null) {
-						return done(err);
-					}
-					expect(this.awsS3.lastCall.args).to.deep.equal([{
-						credentials: {
-							accessKeyId,
-							secretAccessKey: secret
-						}
-					}]);
-					expect(stream).to.equal(this.expectedStream);
-					return done();
-				});
-			});
-
-			return it("accepts byte range", function(done) {
-				const start = 0;
-				const end = 8;
-				const opts = { start, end };
-				return this.S3PersistorManager.getFileStream(this.bucketName, this.key, opts, (err, stream) => {
-					if (err != null) {
-						return done(err);
-					}
-					expect(this.s3Client.getObject.lastCall.args).to.deep.equal([{
-						Bucket: this.bucketName,
-						Key: this.key,
-						Range: `bytes=${start}-${end}`
-					}]);
-					expect(stream).to.equal(this.expectedStream);
-					return done();
-				});
-			});
-		});
-
-		return describe("errors", function() {
-			describe("when the file doesn't exist", function() {
-				beforeEach(function() {
-					return this.s3Request.send.callsFake(() => {
-						return this.s3EventHandlers.httpHeaders(404, {}, this.s3Response, "Not found");
-					});
-				});
-
-				return it("returns a NotFoundError that indicates the bucket and key", function(done) {
-					return this.S3PersistorManager.getFileStream(this.bucketName, this.key, {}, (err, stream) => {
-						expect(err).to.be.instanceof(this.Errors.NotFoundError);
-						const errMsg = this.Errors.NotFoundError.lastCall.args[0];
-						expect(errMsg).to.match(new RegExp(`.*${this.bucketName}.*`));
-						expect(errMsg).to.match(new RegExp(`.*${this.key}.*`));
-						return done();
-					});
-				});
-			});
-
-			describe("when S3 encounters an unkown error", function() {
-				beforeEach(function() {
-					return this.s3Request.send.callsFake(() => {
-						return this.s3EventHandlers.httpHeaders(500, {}, this.s3Response, "Internal server error");
-					});
-				});
-
-				return it("returns an error", function(done) {
-					return this.S3PersistorManager.getFileStream(this.bucketName, this.key, {}, (err, stream) => {
-						expect(err).to.be.instanceof(Error);
-						return done();
-					});
-				});
-			});
-
-			return describe("when the S3 request errors out before receiving HTTP headers", function() {
-				beforeEach(function() {
-					return this.s3Request.send.callsFake(() => {
-						return this.s3EventHandlers.error(new Error("connection failed"));
-					});
-				});
-
-				return it("returns an error", function(done) {
-					return this.S3PersistorManager.getFileStream(this.bucketName, this.key, {}, (err, stream) => {
-						expect(err).to.be.instanceof(Error);
-						return done();
-					});
-				});
-			});
-		});
-	});
-
-	describe("getFileSize", function() {
-		it("should obtain the file size from S3", function(done) {
-			const expectedFileSize = 123;
-			this.s3Client.headObject.yields(new Error(
-				"s3Client.headObject got unexpected arguments"
-			));
-			this.s3Client.headObject.withArgs({
-				Bucket: this.bucketName,
-				Key: this.key
-			}).yields(null, { ContentLength: expectedFileSize });
-
-			return this.S3PersistorManager.getFileSize(this.bucketName, this.key, (err, fileSize) => {
-				if (err != null) {
-					return done(err);
-				}
-				expect(fileSize).to.equal(expectedFileSize);
-				return done();
-			});
-		});
-
-		[403, 404].forEach(statusCode => it(`should throw NotFoundError when S3 responds with ${statusCode}`, function(done) {
-            const error = new Error();
-            error.statusCode = statusCode;
-            this.s3Client.headObject.yields(error);
-
-            return this.S3PersistorManager.getFileSize(this.bucketName, this.key, (err, fileSize) => {
-                expect(err).to.be.an.instanceof(this.Errors.NotFoundError);
-                return done();
-            });
-        }));
-
-		return it("should rethrow any other error", function(done) {
-			const error = new Error();
-			this.s3Client.headObject.yields(error);
-			this.s3Client.headObject.yields(error);
-
-			return this.S3PersistorManager.getFileSize(this.bucketName, this.key, (err, fileSize) => {
-				expect(err).to.equal(error);
-				return done();
-			});
-		});
-	});
-
-	describe("sendFile", function() {
-
-		beforeEach(function() {
-			return this.knoxClient.putFile.returns({on() {}});
-		});
-
-		it("should put file with knox", function(done){
-			this.LocalFileWriter.deleteFile.callsArgWith(1);
-			this.knoxClient.putFile.callsArgWith(2, this.error);
-			return this.S3PersistorManager.sendFile(this.bucketName, this.key, this.fsPath, err=> {
-				this.knoxClient.putFile.calledWith(this.fsPath, this.key).should.equal(true);
-				err.should.equal(this.error);
-				return done();
-			});
-		});
-
-		return it("should delete the file and pass the error with it", function(done){
-			this.LocalFileWriter.deleteFile.callsArgWith(1);
-			this.knoxClient.putFile.callsArgWith(2, this.error);
-			return this.S3PersistorManager.sendFile(this.bucketName, this.key, this.fsPath, err=> {
-				this.knoxClient.putFile.calledWith(this.fsPath, this.key).should.equal(true);
-				err.should.equal(this.error);
-				return done();
-			});
-		});
-	});
-
-	describe("sendStream", function() {
-		beforeEach(function() {
-			this.fsPath = "to/some/where";
-			this.origin =
-				{on() {}};
-			return this.S3PersistorManager.sendFile = sinon.stub().callsArgWith(3);
-		});
-
-		it("should send stream to LocalFileWriter", function(done){
-			this.LocalFileWriter.deleteFile.callsArgWith(1);
-			this.LocalFileWriter.writeStream.callsArgWith(2, null, this.fsPath);
-			return this.S3PersistorManager.sendStream(this.bucketName, this.key, this.origin, () => {
-				this.LocalFileWriter.writeStream.calledWith(this.origin).should.equal(true);
-				return done();
-			});
-		});
-
-		it("should return the error from LocalFileWriter", function(done){
-			this.LocalFileWriter.deleteFile.callsArgWith(1);
-			this.LocalFileWriter.writeStream.callsArgWith(2, this.error);
-			return this.S3PersistorManager.sendStream(this.bucketName, this.key, this.origin, err=> {
-				err.should.equal(this.error);
-				return done();
-			});
-		});
-
-		return it("should send the file to the filestore", function(done){
-			this.LocalFileWriter.deleteFile.callsArgWith(1);
-			this.LocalFileWriter.writeStream.callsArgWith(2);
-			return this.S3PersistorManager.sendStream(this.bucketName, this.key, this.origin, err=> {
-				this.S3PersistorManager.sendFile.called.should.equal(true);
-				return done();
-			});
-		});
-	});
-
-	describe("copyFile", function() {
-		beforeEach(function() {
-			this.sourceKey = "my/key";
-			return this.destKey = "my/dest/key";
-		});
-
-		it("should use AWS SDK to copy file", function(done){
-			this.s3Client.copyObject.callsArgWith(1, this.error);
-			return this.S3PersistorManager.copyFile(this.bucketName, this.sourceKey, this.destKey, err=> {
-				err.should.equal(this.error);
-				this.s3Client.copyObject.calledWith({Bucket: this.bucketName, Key: this.destKey, CopySource: this.bucketName + '/' + this.key}).should.equal(true);
-				return done();
-			});
-		});
-
-		return it("should return a NotFoundError object if the original file does not exist", function(done){
-			const NoSuchKeyError = {code: "NoSuchKey"};
-			this.s3Client.copyObject.callsArgWith(1, NoSuchKeyError);
-			return this.S3PersistorManager.copyFile(this.bucketName, this.sourceKey, this.destKey, err=> {
-				expect(err instanceof this.Errors.NotFoundError).to.equal(true);
-				return done();
-			});
-		});
-	});
-
-	describe("deleteDirectory", () => it("should list the contents passing them onto multi delete", function(done){
-        const data =
-            {Contents: [{Key:"1234"}, {Key: "456"}]};
-        this.knoxClient.list.callsArgWith(1, null, data);
-        this.knoxClient.deleteMultiple.callsArgWith(1);
-        return this.S3PersistorManager.deleteDirectory(this.bucketName, this.key, err=> {
-            this.knoxClient.deleteMultiple.calledWith(["1234","456"]).should.equal(true);
-            return done();
-        });
-    }));
-
-	describe("deleteFile", function() {
-
-		it("should use correct options", function(done){
-			this.request.callsArgWith(1);
-
-			return this.S3PersistorManager.deleteFile(this.bucketName, this.key, err=> {
-				const opts = this.request.args[0][0];
-				assert.deepEqual(opts.aws, {key:this.settings.filestore.s3.key, secret:this.settings.filestore.s3.secret, bucket:this.bucketName});
-				opts.method.should.equal("delete");
-				opts.timeout.should.equal((30*1000));
-				opts.uri.should.equal(`https://${this.bucketName}.s3.amazonaws.com/${this.key}`);
-				return done();
-			});
-		});
-
-		return it("should return the error", function(done){
-			this.request.callsArgWith(1, this.error);
-
-			return this.S3PersistorManager.deleteFile(this.bucketName, this.key, err=> {
-				err.should.equal(this.error);
-				return done();
-			});
-		});
-	});
-
-	describe("checkIfFileExists", function() {
-
-		it("should use correct options", function(done){
-			this.request.callsArgWith(1,  null, {statusCode:200});
-
-			return this.S3PersistorManager.checkIfFileExists(this.bucketName, this.key, err=> {
-				const opts = this.request.args[0][0];
-				assert.deepEqual(opts.aws, {key:this.settings.filestore.s3.key, secret:this.settings.filestore.s3.secret, bucket:this.bucketName});
-				opts.method.should.equal("head");
-				opts.timeout.should.equal((30*1000));
-				opts.uri.should.equal(`https://${this.bucketName}.s3.amazonaws.com/${this.key}`);
-				return done();
-			});
-		});
-
-		it("should return true for a 200", function(done){
-			this.request.callsArgWith(1, null, {statusCode:200});
-
-			return this.S3PersistorManager.checkIfFileExists(this.bucketName, this.key, (err, exists)=> {
-				exists.should.equal(true);
-				return done();
-			});
-		});
-
-		it("should return false for a non 200", function(done){
-			this.request.callsArgWith(1, null, {statusCode:404});
-
-			return this.S3PersistorManager.checkIfFileExists(this.bucketName, this.key, (err, exists)=> {
-				exists.should.equal(false);
-				return done();
-			});
-		});
-
-		return it("should return the error", function(done){
-			this.request.callsArgWith(1, this.error, {});
-
-			return this.S3PersistorManager.checkIfFileExists(this.bucketName, this.key, err=> {
-				err.should.equal(this.error);
-				return done();
-			});
-		});
-	});
-
-	return describe("directorySize", () => it("should sum directory files size", function(done) {
-        const data =
-            {Contents: [ {Size: 1024}, {Size: 2048} ]};
-        this.knoxClient.list.callsArgWith(1, null, data);
-        return this.S3PersistorManager.directorySize(this.bucketName, this.key, (err, totalSize)=> {
-            totalSize.should.equal(3072);
-            return done();
-        });
-    }));
-});
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/S3PersistorManager.js'
+const SandboxedModule = require('sandboxed-module')
+
+describe('S3PersistorManagerTests', function() {
+  beforeEach(function() {
+    this.settings = {
+      filestore: {
+        backend: 's3',
+        s3: {
+          secret: 'secret',
+          key: 'this_key'
+        },
+        stores: {
+          user_files: 'sl_user_files'
+        }
+      }
+    }
+    this.knoxClient = {
+      putFile: sinon.stub(),
+      copyFile: sinon.stub(),
+      list: sinon.stub(),
+      deleteMultiple: sinon.stub(),
+      get: sinon.stub()
+    }
+    this.knox = { createClient: sinon.stub().returns(this.knoxClient) }
+    this.s3EventHandlers = {}
+    this.s3Request = {
+      on: sinon.stub().callsFake((event, callback) => {
+        return (this.s3EventHandlers[event] = callback)
+      }),
+      send: sinon.stub()
+    }
+    this.s3Response = {
+      httpResponse: {
+        createUnbufferedStream: sinon.stub()
+      }
+    }
+    this.s3Client = {
+      copyObject: sinon.stub(),
+      headObject: sinon.stub(),
+      getObject: sinon.stub().returns(this.s3Request)
+    }
+    this.awsS3 = sinon.stub().returns(this.s3Client)
+    this.LocalFileWriter = {
+      writeStream: sinon.stub(),
+      deleteFile: sinon.stub()
+    }
+    this.request = sinon.stub()
+    this.requires = {
+      knox: this.knox,
+      'aws-sdk/clients/s3': this.awsS3,
+      'settings-sharelatex': this.settings,
+      './LocalFileWriter': this.LocalFileWriter,
+      'logger-sharelatex': {
+        log() {},
+        err() {}
+      },
+      request: this.request,
+      './Errors': (this.Errors = { NotFoundError: sinon.stub() })
+    }
+    this.key = 'my/key'
+    this.bucketName = 'my-bucket'
+    this.error = 'my errror'
+    return (this.S3PersistorManager = SandboxedModule.require(modulePath, {
+      requires: this.requires
+    }))
+  })
+
+  describe('getFileStream', function() {
+    describe('success', function() {
+      beforeEach(function() {
+        this.expectedStream = { expectedStream: true }
+        this.expectedStream.on = sinon.stub()
+        this.s3Request.send.callsFake(() => {
+          return this.s3EventHandlers.httpHeaders(
+            200,
+            {},
+            this.s3Response,
+            'OK'
+          )
+        })
+        return this.s3Response.httpResponse.createUnbufferedStream.returns(
+          this.expectedStream
+        )
+      })
+
+      it('returns a stream', function(done) {
+        return this.S3PersistorManager.getFileStream(
+          this.bucketName,
+          this.key,
+          {},
+          (err, stream) => {
+            if (err != null) {
+              return done(err)
+            }
+            expect(stream).to.equal(this.expectedStream)
+            return done()
+          }
+        )
+      })
+
+      it('sets the AWS client up with credentials from settings', function(done) {
+        return this.S3PersistorManager.getFileStream(
+          this.bucketName,
+          this.key,
+          {},
+          (err, stream) => {
+            if (err != null) {
+              return done(err)
+            }
+            expect(this.awsS3.lastCall.args).to.deep.equal([
+              {
+                credentials: {
+                  accessKeyId: this.settings.filestore.s3.key,
+                  secretAccessKey: this.settings.filestore.s3.secret
+                }
+              }
+            ])
+            return done()
+          }
+        )
+      })
+
+      it('fetches the right key from the right bucket', function(done) {
+        return this.S3PersistorManager.getFileStream(
+          this.bucketName,
+          this.key,
+          {},
+          (err, stream) => {
+            if (err != null) {
+              return done(err)
+            }
+            expect(this.s3Client.getObject.lastCall.args).to.deep.equal([
+              {
+                Bucket: this.bucketName,
+                Key: this.key
+              }
+            ])
+            return done()
+          }
+        )
+      })
+
+      it('accepts alternative credentials', function(done) {
+        const accessKeyId = 'that_key'
+        const secret = 'that_secret'
+        const opts = {
+          credentials: {
+            auth_key: accessKeyId,
+            auth_secret: secret
+          }
+        }
+        return this.S3PersistorManager.getFileStream(
+          this.bucketName,
+          this.key,
+          opts,
+          (err, stream) => {
+            if (err != null) {
+              return done(err)
+            }
+            expect(this.awsS3.lastCall.args).to.deep.equal([
+              {
+                credentials: {
+                  accessKeyId,
+                  secretAccessKey: secret
+                }
+              }
+            ])
+            expect(stream).to.equal(this.expectedStream)
+            return done()
+          }
+        )
+      })
+
+      return it('accepts byte range', function(done) {
+        const start = 0
+        const end = 8
+        const opts = { start, end }
+        return this.S3PersistorManager.getFileStream(
+          this.bucketName,
+          this.key,
+          opts,
+          (err, stream) => {
+            if (err != null) {
+              return done(err)
+            }
+            expect(this.s3Client.getObject.lastCall.args).to.deep.equal([
+              {
+                Bucket: this.bucketName,
+                Key: this.key,
+                Range: `bytes=${start}-${end}`
+              }
+            ])
+            expect(stream).to.equal(this.expectedStream)
+            return done()
+          }
+        )
+      })
+    })
+
+    return describe('errors', function() {
+      describe("when the file doesn't exist", function() {
+        beforeEach(function() {
+          return this.s3Request.send.callsFake(() => {
+            return this.s3EventHandlers.httpHeaders(
+              404,
+              {},
+              this.s3Response,
+              'Not found'
+            )
+          })
+        })
+
+        return it('returns a NotFoundError that indicates the bucket and key', function(done) {
+          return this.S3PersistorManager.getFileStream(
+            this.bucketName,
+            this.key,
+            {},
+            (err, stream) => {
+              expect(err).to.be.instanceof(this.Errors.NotFoundError)
+              const errMsg = this.Errors.NotFoundError.lastCall.args[0]
+              expect(errMsg).to.match(new RegExp(`.*${this.bucketName}.*`))
+              expect(errMsg).to.match(new RegExp(`.*${this.key}.*`))
+              return done()
+            }
+          )
+        })
+      })
+
+      describe('when S3 encounters an unkown error', function() {
+        beforeEach(function() {
+          return this.s3Request.send.callsFake(() => {
+            return this.s3EventHandlers.httpHeaders(
+              500,
+              {},
+              this.s3Response,
+              'Internal server error'
+            )
+          })
+        })
+
+        return it('returns an error', function(done) {
+          return this.S3PersistorManager.getFileStream(
+            this.bucketName,
+            this.key,
+            {},
+            (err, stream) => {
+              expect(err).to.be.instanceof(Error)
+              return done()
+            }
+          )
+        })
+      })
+
+      return describe('when the S3 request errors out before receiving HTTP headers', function() {
+        beforeEach(function() {
+          return this.s3Request.send.callsFake(() => {
+            return this.s3EventHandlers.error(new Error('connection failed'))
+          })
+        })
+
+        return it('returns an error', function(done) {
+          return this.S3PersistorManager.getFileStream(
+            this.bucketName,
+            this.key,
+            {},
+            (err, stream) => {
+              expect(err).to.be.instanceof(Error)
+              return done()
+            }
+          )
+        })
+      })
+    })
+  })
+
+  describe('getFileSize', function() {
+    it('should obtain the file size from S3', function(done) {
+      const expectedFileSize = 123
+      this.s3Client.headObject.yields(
+        new Error('s3Client.headObject got unexpected arguments')
+      )
+      this.s3Client.headObject
+        .withArgs({
+          Bucket: this.bucketName,
+          Key: this.key
+        })
+        .yields(null, { ContentLength: expectedFileSize })
+
+      return this.S3PersistorManager.getFileSize(
+        this.bucketName,
+        this.key,
+        (err, fileSize) => {
+          if (err != null) {
+            return done(err)
+          }
+          expect(fileSize).to.equal(expectedFileSize)
+          return done()
+        }
+      )
+    })
+
+    ;[403, 404].forEach(statusCode =>
+      it(`should throw NotFoundError when S3 responds with ${statusCode}`, function(done) {
+        const error = new Error()
+        error.statusCode = statusCode
+        this.s3Client.headObject.yields(error)
+
+        return this.S3PersistorManager.getFileSize(
+          this.bucketName,
+          this.key,
+          (err, fileSize) => {
+            expect(err).to.be.an.instanceof(this.Errors.NotFoundError)
+            return done()
+          }
+        )
+      })
+    )
+
+    return it('should rethrow any other error', function(done) {
+      const error = new Error()
+      this.s3Client.headObject.yields(error)
+      this.s3Client.headObject.yields(error)
+
+      return this.S3PersistorManager.getFileSize(
+        this.bucketName,
+        this.key,
+        (err, fileSize) => {
+          expect(err).to.equal(error)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('sendFile', function() {
+    beforeEach(function() {
+      return this.knoxClient.putFile.returns({ on() {} })
+    })
+
+    it('should put file with knox', function(done) {
+      this.LocalFileWriter.deleteFile.callsArgWith(1)
+      this.knoxClient.putFile.callsArgWith(2, this.error)
+      return this.S3PersistorManager.sendFile(
+        this.bucketName,
+        this.key,
+        this.fsPath,
+        err => {
+          this.knoxClient.putFile
+            .calledWith(this.fsPath, this.key)
+            .should.equal(true)
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+
+    return it('should delete the file and pass the error with it', function(done) {
+      this.LocalFileWriter.deleteFile.callsArgWith(1)
+      this.knoxClient.putFile.callsArgWith(2, this.error)
+      return this.S3PersistorManager.sendFile(
+        this.bucketName,
+        this.key,
+        this.fsPath,
+        err => {
+          this.knoxClient.putFile
+            .calledWith(this.fsPath, this.key)
+            .should.equal(true)
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('sendStream', function() {
+    beforeEach(function() {
+      this.fsPath = 'to/some/where'
+      this.origin = { on() {} }
+      return (this.S3PersistorManager.sendFile = sinon.stub().callsArgWith(3))
+    })
+
+    it('should send stream to LocalFileWriter', function(done) {
+      this.LocalFileWriter.deleteFile.callsArgWith(1)
+      this.LocalFileWriter.writeStream.callsArgWith(2, null, this.fsPath)
+      return this.S3PersistorManager.sendStream(
+        this.bucketName,
+        this.key,
+        this.origin,
+        () => {
+          this.LocalFileWriter.writeStream
+            .calledWith(this.origin)
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
+
+    it('should return the error from LocalFileWriter', function(done) {
+      this.LocalFileWriter.deleteFile.callsArgWith(1)
+      this.LocalFileWriter.writeStream.callsArgWith(2, this.error)
+      return this.S3PersistorManager.sendStream(
+        this.bucketName,
+        this.key,
+        this.origin,
+        err => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+
+    return it('should send the file to the filestore', function(done) {
+      this.LocalFileWriter.deleteFile.callsArgWith(1)
+      this.LocalFileWriter.writeStream.callsArgWith(2)
+      return this.S3PersistorManager.sendStream(
+        this.bucketName,
+        this.key,
+        this.origin,
+        err => {
+          this.S3PersistorManager.sendFile.called.should.equal(true)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('copyFile', function() {
+    beforeEach(function() {
+      this.sourceKey = 'my/key'
+      return (this.destKey = 'my/dest/key')
+    })
+
+    it('should use AWS SDK to copy file', function(done) {
+      this.s3Client.copyObject.callsArgWith(1, this.error)
+      return this.S3PersistorManager.copyFile(
+        this.bucketName,
+        this.sourceKey,
+        this.destKey,
+        err => {
+          err.should.equal(this.error)
+          this.s3Client.copyObject
+            .calledWith({
+              Bucket: this.bucketName,
+              Key: this.destKey,
+              CopySource: this.bucketName + '/' + this.key
+            })
+            .should.equal(true)
+          return done()
+        }
+      )
+    })
+
+    return it('should return a NotFoundError object if the original file does not exist', function(done) {
+      const NoSuchKeyError = { code: 'NoSuchKey' }
+      this.s3Client.copyObject.callsArgWith(1, NoSuchKeyError)
+      return this.S3PersistorManager.copyFile(
+        this.bucketName,
+        this.sourceKey,
+        this.destKey,
+        err => {
+          expect(err instanceof this.Errors.NotFoundError).to.equal(true)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('deleteDirectory', () =>
+    it('should list the contents passing them onto multi delete', function(done) {
+      const data = { Contents: [{ Key: '1234' }, { Key: '456' }] }
+      this.knoxClient.list.callsArgWith(1, null, data)
+      this.knoxClient.deleteMultiple.callsArgWith(1)
+      return this.S3PersistorManager.deleteDirectory(
+        this.bucketName,
+        this.key,
+        err => {
+          this.knoxClient.deleteMultiple
+            .calledWith(['1234', '456'])
+            .should.equal(true)
+          return done()
+        }
+      )
+    }))
+
+  describe('deleteFile', function() {
+    it('should use correct options', function(done) {
+      this.request.callsArgWith(1)
+
+      return this.S3PersistorManager.deleteFile(
+        this.bucketName,
+        this.key,
+        err => {
+          const opts = this.request.args[0][0]
+          assert.deepEqual(opts.aws, {
+            key: this.settings.filestore.s3.key,
+            secret: this.settings.filestore.s3.secret,
+            bucket: this.bucketName
+          })
+          opts.method.should.equal('delete')
+          opts.timeout.should.equal(30 * 1000)
+          opts.uri.should.equal(
+            `https://${this.bucketName}.s3.amazonaws.com/${this.key}`
+          )
+          return done()
+        }
+      )
+    })
+
+    return it('should return the error', function(done) {
+      this.request.callsArgWith(1, this.error)
+
+      return this.S3PersistorManager.deleteFile(
+        this.bucketName,
+        this.key,
+        err => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+  })
+
+  describe('checkIfFileExists', function() {
+    it('should use correct options', function(done) {
+      this.request.callsArgWith(1, null, { statusCode: 200 })
+
+      return this.S3PersistorManager.checkIfFileExists(
+        this.bucketName,
+        this.key,
+        err => {
+          const opts = this.request.args[0][0]
+          assert.deepEqual(opts.aws, {
+            key: this.settings.filestore.s3.key,
+            secret: this.settings.filestore.s3.secret,
+            bucket: this.bucketName
+          })
+          opts.method.should.equal('head')
+          opts.timeout.should.equal(30 * 1000)
+          opts.uri.should.equal(
+            `https://${this.bucketName}.s3.amazonaws.com/${this.key}`
+          )
+          return done()
+        }
+      )
+    })
+
+    it('should return true for a 200', function(done) {
+      this.request.callsArgWith(1, null, { statusCode: 200 })
+
+      return this.S3PersistorManager.checkIfFileExists(
+        this.bucketName,
+        this.key,
+        (err, exists) => {
+          exists.should.equal(true)
+          return done()
+        }
+      )
+    })
+
+    it('should return false for a non 200', function(done) {
+      this.request.callsArgWith(1, null, { statusCode: 404 })
+
+      return this.S3PersistorManager.checkIfFileExists(
+        this.bucketName,
+        this.key,
+        (err, exists) => {
+          exists.should.equal(false)
+          return done()
+        }
+      )
+    })
+
+    return it('should return the error', function(done) {
+      this.request.callsArgWith(1, this.error, {})
+
+      return this.S3PersistorManager.checkIfFileExists(
+        this.bucketName,
+        this.key,
+        err => {
+          err.should.equal(this.error)
+          return done()
+        }
+      )
+    })
+  })
+
+  return describe('directorySize', () =>
+    it('should sum directory files size', function(done) {
+      const data = { Contents: [{ Size: 1024 }, { Size: 2048 }] }
+      this.knoxClient.list.callsArgWith(1, null, data)
+      return this.S3PersistorManager.directorySize(
+        this.bucketName,
+        this.key,
+        (err, totalSize) => {
+          totalSize.should.equal(3072)
+          return done()
+        }
+      )
+    }))
+})

+ 78 - 64
services/filestore/test/unit/js/SafeExecTests.js

@@ -9,73 +9,87 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/SafeExec.js";
-const SandboxedModule = require('sandboxed-module');
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/SafeExec.js'
+const SandboxedModule = require('sandboxed-module')
 
-describe("SafeExec", function() {
+describe('SafeExec', function() {
+  beforeEach(function() {
+    this.settings = { enableConversions: true }
+    this.safe_exec = SandboxedModule.require(modulePath, {
+      requires: {
+        'logger-sharelatex': {
+          log() {},
+          err() {}
+        },
+        'settings-sharelatex': this.settings
+      }
+    })
+    return (this.options = { timeout: 10 * 1000, killSignal: 'SIGTERM' })
+  })
 
-	beforeEach(function() {
-		this.settings = 
-			{enableConversions:true};
-		this.safe_exec = SandboxedModule.require(modulePath, { requires: {
-			"logger-sharelatex": {
-				log() {},
-				err() {}
-			},
-			"settings-sharelatex": this.settings
-		}
-	}
-		);
-		return this.options = {timeout: 10*1000, killSignal: "SIGTERM" };});
+  return describe('safe_exec', function() {
+    it('should execute a valid command', function(done) {
+      return this.safe_exec(
+        ['/bin/echo', 'hello'],
+        this.options,
+        (err, stdout, stderr) => {
+          stdout.should.equal('hello\n')
+          should.not.exist(err)
+          return done()
+        }
+      )
+    })
 
-	return describe("safe_exec", function() {
+    it('should error when conversions are disabled', function(done) {
+      this.settings.enableConversions = false
+      return this.safe_exec(
+        ['/bin/echo', 'hello'],
+        this.options,
+        (err, stdout, stderr) => {
+          expect(err).to.exist
+          return done()
+        }
+      )
+    })
 
-		it("should execute a valid command", function(done) {
-			return this.safe_exec(["/bin/echo", "hello"], this.options, (err, stdout, stderr) => {
-				stdout.should.equal("hello\n");
-				should.not.exist(err);
-				return done();
-			});
-		});
+    it('should execute a command with non-zero exit status', function(done) {
+      return this.safe_exec(
+        ['/usr/bin/env', 'false'],
+        this.options,
+        (err, stdout, stderr) => {
+          stdout.should.equal('')
+          stderr.should.equal('')
+          err.message.should.equal('exit status 1')
+          return done()
+        }
+      )
+    })
 
-		it("should error when conversions are disabled", function(done) {
-			this.settings.enableConversions = false;
-			return this.safe_exec(["/bin/echo", "hello"], this.options, (err, stdout, stderr) => {
-				expect(err).to.exist;
-				return done();
-			});
-		});
+    it('should handle an invalid command', function(done) {
+      return this.safe_exec(
+        ['/bin/foobar'],
+        this.options,
+        (err, stdout, stderr) => {
+          err.code.should.equal('ENOENT')
+          return done()
+        }
+      )
+    })
 
-		it("should execute a command with non-zero exit status", function(done) {
-			return this.safe_exec(["/usr/bin/env", "false"], this.options, (err, stdout, stderr) => {
-				stdout.should.equal("");
-				stderr.should.equal("");
-				err.message.should.equal("exit status 1");
-				return done();
-			});
-		});
-
-		it("should handle an invalid command", function(done) {
-			return this.safe_exec(["/bin/foobar"], this.options, (err, stdout, stderr) => {
-				err.code.should.equal("ENOENT");
-				return done();
-			});
-		});
-
-		return it("should handle a command that runs too long", function(done) {
-			return this.safe_exec(["/bin/sleep", "10"], {timeout: 500, killSignal: "SIGTERM"}, (err, stdout, stderr) => {
-				err.should.equal("SIGTERM");
-				return done();
-			});
-		});
-	});
-});
+    return it('should handle a command that runs too long', function(done) {
+      return this.safe_exec(
+        ['/bin/sleep', '10'],
+        { timeout: 500, killSignal: 'SIGTERM' },
+        (err, stdout, stderr) => {
+          err.should.equal('SIGTERM')
+          return done()
+        }
+      )
+    })
+  })
+})

+ 18 - 20
services/filestore/test/unit/js/SettingsTests.js

@@ -9,27 +9,25 @@
  * DS102: Remove unnecessary code created because of implicit returns
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
-const {
-    assert
-} = require("chai");
-const sinon = require('sinon');
-const chai = require('chai');
-const should = chai.should();
-const {
-    expect
-} = chai;
-const modulePath = "../../../app/js/BucketController.js";
+const { assert } = require('chai')
+const sinon = require('sinon')
+const chai = require('chai')
+const should = chai.should()
+const { expect } = chai
+const modulePath = '../../../app/js/BucketController.js'
 
-describe("Settings", () => describe("s3", () => it("should use JSONified env var if present", function(done){
-    const s3_settings = {
+describe('Settings', () =>
+  describe('s3', () =>
+    it('should use JSONified env var if present', function(done) {
+      const s3_settings = {
         bucket1: {
-            auth_key: 'bucket1_key',
-            auth_secret: 'bucket1_secret'
+          auth_key: 'bucket1_key',
+          auth_secret: 'bucket1_secret'
         }
-    };
-    process.env.S3_BUCKET_CREDENTIALS = JSON.stringify(s3_settings);
+      }
+      process.env.S3_BUCKET_CREDENTIALS = JSON.stringify(s3_settings)
 
-    const settings = require("settings-sharelatex");
-    expect(settings.filestore.s3BucketCreds).to.deep.equal(s3_settings);
-    return done();
-})));
+      const settings = require('settings-sharelatex')
+      expect(settings.filestore.s3BucketCreds).to.deep.equal(s3_settings)
+      return done()
+    })))