|
|
@@ -0,0 +1,249 @@
|
|
|
+sinon = require 'sinon'
|
|
|
+chai = require 'chai'
|
|
|
+
|
|
|
+should = chai.should()
|
|
|
+expect = chai.expect
|
|
|
+
|
|
|
+modulePath = "../../../app/js/AWSSDKPersistorManager.js"
|
|
|
+SandboxedModule = require 'sandboxed-module'
|
|
|
+
|
|
|
+describe "AWSSDKPersistorManager", ->
|
|
|
+ beforeEach ->
|
|
|
+ @settings =
|
|
|
+ filestore:
|
|
|
+ backend: "aws-sdk"
|
|
|
+ @s3 =
|
|
|
+ upload: sinon.stub()
|
|
|
+ getObject: sinon.stub()
|
|
|
+ copyObject: sinon.stub()
|
|
|
+ deleteObject: sinon.stub()
|
|
|
+ listObjects: sinon.stub()
|
|
|
+ deleteObjects: sinon.stub()
|
|
|
+ headObject: sinon.stub()
|
|
|
+ @awssdk =
|
|
|
+ S3: sinon.stub().returns @s3
|
|
|
+
|
|
|
+ @requires =
|
|
|
+ "aws-sdk": @awssdk
|
|
|
+ "settings-sharelatex": @settings
|
|
|
+ "logger-sharelatex":
|
|
|
+ log:->
|
|
|
+ err:->
|
|
|
+ "fs": @fs =
|
|
|
+ createReadStream: sinon.stub()
|
|
|
+ "./Errors": @Errors =
|
|
|
+ NotFoundError: sinon.stub()
|
|
|
+ @key = "my/key"
|
|
|
+ @bucketName = "my-bucket"
|
|
|
+ @error = "my error"
|
|
|
+ @AWSSDKPersistorManager = SandboxedModule.require modulePath, requires: @requires
|
|
|
+
|
|
|
+ describe "sendFile", ->
|
|
|
+ beforeEach ->
|
|
|
+ @stream = {}
|
|
|
+ @fsPath = "/usr/local/some/file"
|
|
|
+ @fs.createReadStream.returns @stream
|
|
|
+
|
|
|
+ it "should put the file with s3.upload", (done) ->
|
|
|
+ @s3.upload.callsArgWith 1
|
|
|
+ @AWSSDKPersistorManager.sendFile @bucketName, @key, @fsPath, (err) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.upload.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.upload.calledWith Bucket: @bucketName, Key: @key, Body: @stream)
|
|
|
+ , "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.upload", (done) ->
|
|
|
+ @s3.upload.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.sendFile @bucketName, @key, @fsPath, (err) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+
|
|
|
+ describe "sendStream", ->
|
|
|
+ beforeEach ->
|
|
|
+ @stream = {}
|
|
|
+
|
|
|
+ it "should put the file with s3.upload", (done) ->
|
|
|
+ @s3.upload.callsArgWith 1
|
|
|
+ @AWSSDKPersistorManager.sendStream @bucketName, @key, @stream, (err) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.upload.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.upload.calledWith Bucket: @bucketName, Key: @key, Body: @stream),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.upload", (done) ->
|
|
|
+ @s3.upload.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.sendStream @bucketName, @key, @stream, (err) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "getFileStream", ->
|
|
|
+ beforeEach ->
|
|
|
+ @opts = {}
|
|
|
+ @stream = {}
|
|
|
+ @read_stream =
|
|
|
+ on: @read_stream_on = sinon.stub()
|
|
|
+ @object =
|
|
|
+ createReadStream: sinon.stub().returns @read_stream
|
|
|
+ @s3.getObject.returns @object
|
|
|
+
|
|
|
+ it "should return a stream from s3.getObject", (done) ->
|
|
|
+ @read_stream_on.withArgs('readable').callsArgWith 1
|
|
|
+
|
|
|
+ @AWSSDKPersistorManager.getFileStream @bucketName, @key, @opts, (err, stream) =>
|
|
|
+ expect(@read_stream_on.calledTwice)
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(stream, "returned the stream").to.equal @read_stream
|
|
|
+ expect((@s3.getObject.calledWith Bucket: @bucketName, Key: @key),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "with start and end options", ->
|
|
|
+ beforeEach ->
|
|
|
+ @opts =
|
|
|
+ start: 0
|
|
|
+ end: 8
|
|
|
+ it "should pass headers to the s3.GetObject", (done) ->
|
|
|
+ @read_stream_on.withArgs('readable').callsArgWith 1
|
|
|
+ @AWSSDKPersistorManager.getFileStream @bucketName, @key, @opts, (err, stream) =>
|
|
|
+ expect((@s3.getObject.calledWith Bucket: @bucketName, Key: @key, Range: 'bytes=0-8'),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "error conditions", ->
|
|
|
+ describe "when the file doesn't exist", ->
|
|
|
+ beforeEach ->
|
|
|
+ @error = new Error()
|
|
|
+ @error.code = 'NoSuchKey'
|
|
|
+ it "should produce a NotFoundError", (done) ->
|
|
|
+ @read_stream_on.withArgs('error').callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.getFileStream @bucketName, @key, @opts, (err, stream) =>
|
|
|
+ expect(stream).to.not.be.ok
|
|
|
+ expect(err).to.be.ok
|
|
|
+ expect(err instanceof @Errors.NotFoundError, "error is a correct instance").to.equal true
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "when there is some other error", ->
|
|
|
+ beforeEach ->
|
|
|
+ @error = new Error()
|
|
|
+ it "should dispatch the error from s3 object stream", (done) ->
|
|
|
+ @read_stream_on.withArgs('error').callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.getFileStream @bucketName, @key, @opts, (err, stream) =>
|
|
|
+ expect(stream).to.not.be.ok
|
|
|
+ expect(err).to.be.ok
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "copyFile", ->
|
|
|
+ beforeEach ->
|
|
|
+ @destKey = "some/key"
|
|
|
+ @stream = {}
|
|
|
+
|
|
|
+ it "should copy the file with s3.copyObject", (done) ->
|
|
|
+ @s3.copyObject.callsArgWith 1
|
|
|
+ @AWSSDKPersistorManager.copyFile @bucketName, @key, @destKey, (err) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.copyObject.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.copyObject.calledWith Bucket: @bucketName, Key: @destKey, CopySource: @bucketName + '/' + @key),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.copyObject", (done) ->
|
|
|
+ @s3.copyObject.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.copyFile @bucketName, @key, @destKey, (err) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "deleteFile", ->
|
|
|
+ it "should delete the file with s3.deleteObject", (done) ->
|
|
|
+ @s3.deleteObject.callsArgWith 1
|
|
|
+ @AWSSDKPersistorManager.deleteFile @bucketName, @key, (err) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.deleteObject.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.deleteObject.calledWith Bucket: @bucketName, Key: @key),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.deleteObject", (done) ->
|
|
|
+ @s3.deleteObject.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.deleteFile @bucketName, @key, (err) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "deleteDirectory", ->
|
|
|
+
|
|
|
+ it "should list the directory content using s3.listObjects", (done) ->
|
|
|
+ @s3.listObjects.callsArgWith 1, null, Contents: []
|
|
|
+ @AWSSDKPersistorManager.deleteDirectory @bucketName, @key, (err) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.listObjects.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.listObjects.calledWith Bucket: @bucketName, Prefix: @key),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.listObjects", (done) ->
|
|
|
+ @s3.listObjects.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.deleteDirectory @bucketName, @key, (err) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+ describe "with directory content", ->
|
|
|
+ beforeEach ->
|
|
|
+ @fileList = [
|
|
|
+ Key: 'foo'
|
|
|
+ , Key: 'bar'
|
|
|
+ , Key: 'baz'
|
|
|
+ ]
|
|
|
+
|
|
|
+ it "should forward the file keys to s3.deleteObjects", (done) ->
|
|
|
+ @s3.listObjects.callsArgWith 1, null, Contents: @fileList
|
|
|
+ @s3.deleteObjects.callsArgWith 1
|
|
|
+ @AWSSDKPersistorManager.deleteDirectory @bucketName, @key, (err) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.deleteObjects.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.deleteObjects.calledWith
|
|
|
+ Bucket: @bucketName
|
|
|
+ Delete:
|
|
|
+ Quiet: true
|
|
|
+ Objects: @fileList),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.deleteObjects", (done) ->
|
|
|
+ @s3.listObjects.callsArgWith 1, null, Contents: @fileList
|
|
|
+ @s3.deleteObjects.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.deleteDirectory @bucketName, @key, (err) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|
|
|
+
|
|
|
+
|
|
|
+ describe "checkIfFileExists", ->
|
|
|
+
|
|
|
+ it "should check for the file with s3.headObject", (done) ->
|
|
|
+ @s3.headObject.callsArgWith 1, null, {}
|
|
|
+ @AWSSDKPersistorManager.checkIfFileExists @bucketName, @key, (err, exists) =>
|
|
|
+ expect(err).to.not.be.ok
|
|
|
+ expect(@s3.headObject.calledOnce, "called only once").to.be.true
|
|
|
+ expect((@s3.headObject.calledWith Bucket: @bucketName, Key: @key),
|
|
|
+ "called with correct arguments").to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should return false on an inexistant file", (done) ->
|
|
|
+ @s3.headObject.callsArgWith 1, null, {}
|
|
|
+ @AWSSDKPersistorManager.checkIfFileExists @bucketName, @key, (err, exists) =>
|
|
|
+ expect(exists).to.be.false
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should return true on an existing file", (done) ->
|
|
|
+ @s3.headObject.callsArgWith 1, null, ETag: "etag"
|
|
|
+ @AWSSDKPersistorManager.checkIfFileExists @bucketName, @key, (err, exists) =>
|
|
|
+ expect(exists).to.be.true
|
|
|
+ done()
|
|
|
+
|
|
|
+ it "should dispatch the error from s3.headObject", (done) ->
|
|
|
+ @s3.headObject.callsArgWith 1, @error
|
|
|
+ @AWSSDKPersistorManager.checkIfFileExists @bucketName, @key, (err, exists) =>
|
|
|
+ expect(err).to.equal @error
|
|
|
+ done()
|