|
@@ -1,618 +1,720 @@
|
|
|
-/* eslint-disable
|
|
|
|
|
- handle-callback-err,
|
|
|
|
|
- no-return-assign,
|
|
|
|
|
- no-unused-vars,
|
|
|
|
|
-*/
|
|
|
|
|
-// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
|
-// Fix any style issues and re-enable lint.
|
|
|
|
|
-/*
|
|
|
|
|
- * decaffeinate suggestions:
|
|
|
|
|
- * DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
|
- * 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 sinon = require('sinon')
|
|
|
const chai = require('chai')
|
|
const chai = require('chai')
|
|
|
-const should = chai.should()
|
|
|
|
|
const { expect } = chai
|
|
const { expect } = chai
|
|
|
const modulePath = '../../../app/js/S3PersistorManager.js'
|
|
const modulePath = '../../../app/js/S3PersistorManager.js'
|
|
|
const SandboxedModule = require('sandboxed-module')
|
|
const SandboxedModule = require('sandboxed-module')
|
|
|
|
|
|
|
|
|
|
+const Errors = require('../../../app/js/Errors')
|
|
|
|
|
+
|
|
|
describe('S3PersistorManagerTests', function() {
|
|
describe('S3PersistorManagerTests', function() {
|
|
|
- beforeEach(function() {
|
|
|
|
|
- this.settings = {
|
|
|
|
|
- filestore: {
|
|
|
|
|
- backend: 's3',
|
|
|
|
|
- s3: {
|
|
|
|
|
- secret: 'secret',
|
|
|
|
|
- key: 'this_key'
|
|
|
|
|
- },
|
|
|
|
|
- stores: {
|
|
|
|
|
- user_files: 'sl_user_files'
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const 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()
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ const filename = '/wombat/potato.tex'
|
|
|
|
|
+ const bucket = 'womBucket'
|
|
|
|
|
+ const key = 'monKey'
|
|
|
|
|
+ const destKey = 'donKey'
|
|
|
|
|
+ const objectSize = 5555
|
|
|
|
|
+ const genericError = new Error('guru meditation error')
|
|
|
|
|
+ const files = [
|
|
|
|
|
+ { Key: 'llama', Size: 11 },
|
|
|
|
|
+ { Key: 'hippo', Size: 22 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ const filesSize = 33
|
|
|
|
|
+
|
|
|
|
|
+ let Metrics,
|
|
|
|
|
+ S3,
|
|
|
|
|
+ Fs,
|
|
|
|
|
+ Meter,
|
|
|
|
|
+ MeteredStream,
|
|
|
|
|
+ ReadStream,
|
|
|
|
|
+ S3PersistorManager,
|
|
|
|
|
+ S3Client,
|
|
|
|
|
+ S3ReadStream,
|
|
|
|
|
+ S3NotFoundError,
|
|
|
|
|
+ FileNotFoundError,
|
|
|
|
|
+ EmptyPromise
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ EmptyPromise = {
|
|
|
|
|
+ promise: sinon.stub().resolves()
|
|
|
}
|
|
}
|
|
|
- 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()
|
|
|
|
|
|
|
+
|
|
|
|
|
+ Metrics = {
|
|
|
|
|
+ count: sinon.stub()
|
|
|
}
|
|
}
|
|
|
- this.s3Response = {
|
|
|
|
|
- httpResponse: {
|
|
|
|
|
- createUnbufferedStream: sinon.stub()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ ReadStream = {
|
|
|
|
|
+ pipe: sinon.stub().returns('readStream')
|
|
|
}
|
|
}
|
|
|
- this.s3Client = {
|
|
|
|
|
- copyObject: sinon.stub(),
|
|
|
|
|
- headObject: sinon.stub(),
|
|
|
|
|
- getObject: sinon.stub().returns(this.s3Request)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ FileNotFoundError = new Error('File not found')
|
|
|
|
|
+ FileNotFoundError.code = 'ENOENT'
|
|
|
|
|
+
|
|
|
|
|
+ Fs = {
|
|
|
|
|
+ createReadStream: sinon.stub().returns(ReadStream)
|
|
|
}
|
|
}
|
|
|
- this.awsS3 = sinon.stub().returns(this.s3Client)
|
|
|
|
|
- this.LocalFileWriter = {
|
|
|
|
|
- writeStream: sinon.stub(),
|
|
|
|
|
- deleteFile: sinon.stub()
|
|
|
|
|
|
|
+
|
|
|
|
|
+ MeteredStream = {
|
|
|
|
|
+ on: sinon.stub(),
|
|
|
|
|
+ bytes: objectSize
|
|
|
}
|
|
}
|
|
|
- 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() })
|
|
|
|
|
|
|
+ MeteredStream.on.withArgs('finish').yields()
|
|
|
|
|
+ Meter = sinon.stub().returns(MeteredStream)
|
|
|
|
|
+
|
|
|
|
|
+ S3NotFoundError = new Error('not found')
|
|
|
|
|
+ S3NotFoundError.code = 'NoSuchKey'
|
|
|
|
|
+
|
|
|
|
|
+ S3ReadStream = {
|
|
|
|
|
+ on: sinon.stub(),
|
|
|
|
|
+ pipe: sinon.stub().returns('s3Stream'),
|
|
|
|
|
+ removeListener: sinon.stub()
|
|
|
|
|
+ }
|
|
|
|
|
+ S3ReadStream.on.withArgs('readable').yields()
|
|
|
|
|
+ S3Client = {
|
|
|
|
|
+ getObject: sinon.stub().returns({
|
|
|
|
|
+ createReadStream: sinon.stub().returns(S3ReadStream)
|
|
|
|
|
+ }),
|
|
|
|
|
+ headObject: sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().resolves({
|
|
|
|
|
+ ContentLength: objectSize
|
|
|
|
|
+ })
|
|
|
|
|
+ }),
|
|
|
|
|
+ listObjects: sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().resolves({
|
|
|
|
|
+ Contents: files
|
|
|
|
|
+ })
|
|
|
|
|
+ }),
|
|
|
|
|
+ upload: sinon.stub().returns(EmptyPromise),
|
|
|
|
|
+ copyObject: sinon.stub().returns(EmptyPromise),
|
|
|
|
|
+ deleteObject: sinon.stub().returns(EmptyPromise),
|
|
|
|
|
+ deleteObjects: sinon.stub().returns(EmptyPromise)
|
|
|
}
|
|
}
|
|
|
- this.key = 'my/key'
|
|
|
|
|
- this.bucketName = 'my-bucket'
|
|
|
|
|
- this.error = 'my errror'
|
|
|
|
|
- return (this.S3PersistorManager = SandboxedModule.require(modulePath, {
|
|
|
|
|
- requires: this.requires
|
|
|
|
|
- }))
|
|
|
|
|
|
|
+ S3 = sinon.stub().returns(S3Client)
|
|
|
|
|
+
|
|
|
|
|
+ S3PersistorManager = SandboxedModule.require(modulePath, {
|
|
|
|
|
+ requires: {
|
|
|
|
|
+ 'aws-sdk/clients/s3': S3,
|
|
|
|
|
+ 'settings-sharelatex': settings,
|
|
|
|
|
+ './Errors': Errors,
|
|
|
|
|
+ fs: Fs,
|
|
|
|
|
+ 'stream-meter': Meter,
|
|
|
|
|
+ 'logger-sharelatex': {
|
|
|
|
|
+ log() {},
|
|
|
|
|
+ err() {}
|
|
|
|
|
+ },
|
|
|
|
|
+ 'metrics-sharelatex': Metrics
|
|
|
|
|
+ },
|
|
|
|
|
+ globals: { console }
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('getFileStream', function() {
|
|
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
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ describe('when called with valid parameters', function() {
|
|
|
|
|
+ let stream
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ stream = await S3PersistorManager.promises.getFileStream(bucket, key)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('returns a stream', function() {
|
|
|
|
|
+ expect(stream).to.equal('s3Stream')
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('sets the AWS client up with credentials from settings', function() {
|
|
|
|
|
+ expect(S3).to.have.been.calledWith({
|
|
|
|
|
+ credentials: {
|
|
|
|
|
+ accessKeyId: settings.filestore.s3.key,
|
|
|
|
|
+ secretAccessKey: settings.filestore.s3.secret
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('fetches the right key from the right bucket', function() {
|
|
|
|
|
+ expect(S3Client.getObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ it('pipes the stream through the meter', function() {
|
|
|
|
|
+ expect(S3ReadStream.pipe).to.have.been.calledWith(MeteredStream)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ it('records an ingress metric', function() {
|
|
|
|
|
+ expect(Metrics.count).to.have.been.calledWith('s3.ingress', objectSize)
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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'
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ describe('when called with a byte range', function() {
|
|
|
|
|
+ let stream
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ stream = await S3PersistorManager.promises.getFileStream(bucket, key, {
|
|
|
|
|
+ start: 5,
|
|
|
|
|
+ end: 10
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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'
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ it('returns a stream', function() {
|
|
|
|
|
+ expect(stream).to.equal('s3Stream')
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ it('passes the byte range on to S3', function() {
|
|
|
|
|
+ expect(S3Client.getObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key,
|
|
|
|
|
+ Range: 'bytes=5-10'
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- 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'))
|
|
|
|
|
- })
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ describe("when the file doesn't exist", function() {
|
|
|
|
|
+ let error, stream
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3ReadStream.on = sinon.stub()
|
|
|
|
|
+ S3ReadStream.on.withArgs('error').yields(S3NotFoundError)
|
|
|
|
|
+ try {
|
|
|
|
|
+ stream = await S3PersistorManager.promises.getFileStream(bucket, key)
|
|
|
|
|
+ console.log(stream)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ it('does not return a stream', function() {
|
|
|
|
|
+ expect(stream).not.to.exist
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('throws a NotFoundError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('wraps the error from S3', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(S3NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('stores the bucket and key in the error', function() {
|
|
|
|
|
+ expect(error.info).to.deep.equal({ Bucket: bucket, Key: key })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe('when S3 encounters an unkown error', function() {
|
|
|
|
|
+ let error, stream
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3ReadStream.on = sinon.stub()
|
|
|
|
|
+ S3ReadStream.on.withArgs('error').yields(genericError)
|
|
|
|
|
+ try {
|
|
|
|
|
+ stream = await S3PersistorManager.promises.getFileStream(bucket, key)
|
|
|
|
|
+ console.log(stream)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('does not return a stream', function() {
|
|
|
|
|
+ expect(stream).not.to.exist
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('throws a ReadError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('wraps the error from S3', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('stores the bucket and key in the error', function() {
|
|
|
|
|
+ expect(error.info).to.deep.equal({ Bucket: bucket, Key: key })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('getFileSize', function() {
|
|
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
|
|
|
|
|
|
|
+ describe('when called with valid parameters', function() {
|
|
|
|
|
+ let size
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ size = await S3PersistorManager.promises.getFileSize(bucket, key)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should return the object size', function() {
|
|
|
|
|
+ expect(size).to.equal(objectSize)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should pass the bucket and key to S3', function() {
|
|
|
|
|
+ expect(S3Client.headObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: 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()
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe('when the object is not found', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.headObject = sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().rejects(S3NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.getFileSize(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should return a NotFoundError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should wrap the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(S3NotFoundError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
- ;[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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+
|
|
|
|
|
+ describe('when S3 returns an error', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.headObject = sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().rejects(genericError)
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.getFileSize(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
|
|
+ }
|
|
|
})
|
|
})
|
|
|
- )
|
|
|
|
|
|
|
|
|
|
- return it('should rethrow any other error', function(done) {
|
|
|
|
|
- const error = new Error()
|
|
|
|
|
- this.s3Client.headObject.yields(error)
|
|
|
|
|
- this.s3Client.headObject.yields(error)
|
|
|
|
|
|
|
+ it('should return a ReadError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- return this.S3PersistorManager.getFileSize(
|
|
|
|
|
- this.bucketName,
|
|
|
|
|
- this.key,
|
|
|
|
|
- (err, fileSize) => {
|
|
|
|
|
- expect(err).to.equal(error)
|
|
|
|
|
- return done()
|
|
|
|
|
|
|
+ it('should wrap the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe('sendStream', function() {
|
|
|
|
|
+ describe('with valid parameters', function() {
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ return S3PersistorManager.promises.sendStream(bucket, key, ReadStream)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should upload the stream', function() {
|
|
|
|
|
+ expect(S3Client.upload).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key,
|
|
|
|
|
+ Body: 'readStream'
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should meter the stream', function() {
|
|
|
|
|
+ expect(ReadStream.pipe).to.have.been.calledWith(MeteredStream)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should record an egress metric', function() {
|
|
|
|
|
+ expect(Metrics.count).to.have.been.calledWith('s3.egress', objectSize)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe('when the upload fails', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.upload = sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().rejects(genericError)
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.sendStream(bucket, key, ReadStream)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('throws a WriteError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.WriteError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('sendFile', function() {
|
|
describe('sendFile', function() {
|
|
|
- beforeEach(function() {
|
|
|
|
|
- return this.knoxClient.putFile.returns({ on() {} })
|
|
|
|
|
|
|
+ describe('with valid parameters', function() {
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ return S3PersistorManager.promises.sendFile(bucket, key, filename)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should create a read stream for the file', function() {
|
|
|
|
|
+ expect(Fs.createReadStream).to.have.been.calledWith(filename)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should upload the stream', function() {
|
|
|
|
|
+ expect(S3Client.upload).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key,
|
|
|
|
|
+ Body: 'readStream'
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
|
|
+ describe('when the file does not exist', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ Fs.createReadStream = sinon.stub().throws(FileNotFoundError)
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.sendFile(bucket, key, filename)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('returns a NotFoundError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('wraps the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(FileNotFoundError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('when reading the file throws an error', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ Fs.createReadStream = sinon.stub().throws(genericError)
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.sendFile(bucket, key, filename)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('returns a ReadError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('wraps the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- describe('sendStream', function() {
|
|
|
|
|
- beforeEach(function() {
|
|
|
|
|
- this.fsPath = 'to/some/where'
|
|
|
|
|
- this.origin = { on() {} }
|
|
|
|
|
- return (this.S3PersistorManager.sendFile = sinon.stub().callsArgWith(3))
|
|
|
|
|
|
|
+ describe('copyFile', function() {
|
|
|
|
|
+ describe('with valid parameters', function() {
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ return S3PersistorManager.promises.copyFile(bucket, key, destKey)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should copy the object', function() {
|
|
|
|
|
+ expect(S3Client.copyObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: destKey,
|
|
|
|
|
+ CopySource: `${bucket}/${key}`
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
|
|
+ describe('when the file does not exist', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.copyObject = sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().rejects(S3NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.copyFile(bucket, key, destKey)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should throw a NotFoundError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.NotFoundError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ describe('deleteFile', function() {
|
|
|
|
|
+ describe('with valid parameters', function() {
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ return S3PersistorManager.promises.deleteFile(bucket, key)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should delete the object', function() {
|
|
|
|
|
+ expect(S3Client.deleteObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('when the file does not exist', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.deleteObject = sinon.stub().returns({
|
|
|
|
|
+ promise: sinon.stub().rejects(S3NotFoundError)
|
|
|
|
|
+ })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.deleteFile(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should throw a NotFoundError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.NotFoundError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- describe('copyFile', function() {
|
|
|
|
|
- beforeEach(function() {
|
|
|
|
|
- this.sourceKey = 'my/key'
|
|
|
|
|
- return (this.destKey = 'my/dest/key')
|
|
|
|
|
|
|
+ describe('deleteDirectory', function() {
|
|
|
|
|
+ describe('with valid parameters', function() {
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ return S3PersistorManager.promises.deleteDirectory(bucket, key)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should list the objects in the directory', function() {
|
|
|
|
|
+ expect(S3Client.listObjects).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Prefix: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should delete the objects using their keys', function() {
|
|
|
|
|
+ expect(S3Client.deleteObjects).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Delete: {
|
|
|
|
|
+ Objects: [{ Key: 'llama' }, { Key: 'hippo' }],
|
|
|
|
|
+ Quiet: true
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ describe('when there are no files', function() {
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.listObjects = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().resolves({ Contents: [] }) })
|
|
|
|
|
+ return S3PersistorManager.promises.deleteDirectory(bucket, key)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should list the objects in the directory', function() {
|
|
|
|
|
+ expect(S3Client.listObjects).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Prefix: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should not try to delete any objects', function() {
|
|
|
|
|
+ expect(S3Client.deleteObjects).not.to.have.been.called
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
|
|
+ describe('when there is an error listing the objects', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.listObjects = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().rejects(genericError) })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.deleteDirectory(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should generate a ReadError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should wrap the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should not try to delete any objects', function() {
|
|
|
|
|
+ expect(S3Client.deleteObjects).not.to.have.been.called
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('when there is an error deleting the objects', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.deleteObjects = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().rejects(genericError) })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.deleteDirectory(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should generate a WriteError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.WriteError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should wrap the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- 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('directorySize', function() {
|
|
|
|
|
+ describe('with valid parameters', function() {
|
|
|
|
|
+ let size
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ size = await S3PersistorManager.promises.getDirectorySize(bucket, key)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should list the objects in the directory', function() {
|
|
|
|
|
+ expect(S3Client.listObjects).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Prefix: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should return the directory size', function() {
|
|
|
|
|
+ expect(size).to.equal(filesSize)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- return it('should return the error', function(done) {
|
|
|
|
|
- this.request.callsArgWith(1, this.error)
|
|
|
|
|
|
|
+ describe('when there are no files', function() {
|
|
|
|
|
+ let size
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.listObjects = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().resolves({ Contents: [] }) })
|
|
|
|
|
+ size = await S3PersistorManager.promises.getDirectorySize(bucket, key)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should list the objects in the directory', function() {
|
|
|
|
|
+ expect(S3Client.listObjects).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Prefix: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should return zero', function() {
|
|
|
|
|
+ expect(size).to.equal(0)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- return this.S3PersistorManager.deleteFile(
|
|
|
|
|
- this.bucketName,
|
|
|
|
|
- this.key,
|
|
|
|
|
- err => {
|
|
|
|
|
- err.should.equal(this.error)
|
|
|
|
|
- return done()
|
|
|
|
|
|
|
+ describe('when there is an error listing the objects', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.listObjects = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().rejects(genericError) })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.getDirectorySize(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should generate a ReadError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should wrap the error', function() {
|
|
|
|
|
+ expect(error.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
describe('checkIfFileExists', function() {
|
|
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()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ describe('when the file exists', function() {
|
|
|
|
|
+ let exists
|
|
|
|
|
|
|
|
- it('should return true for a 200', function(done) {
|
|
|
|
|
- this.request.callsArgWith(1, null, { statusCode: 200 })
|
|
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ exists = await S3PersistorManager.promises.checkIfFileExists(
|
|
|
|
|
+ bucket,
|
|
|
|
|
+ key
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- return this.S3PersistorManager.checkIfFileExists(
|
|
|
|
|
- this.bucketName,
|
|
|
|
|
- this.key,
|
|
|
|
|
- (err, exists) => {
|
|
|
|
|
- exists.should.equal(true)
|
|
|
|
|
- return done()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ it('should get the object header', function() {
|
|
|
|
|
+ expect(S3Client.headObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should return that the file exists', function() {
|
|
|
|
|
+ expect(exists).to.equal(true)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('should return false for a non 200', function(done) {
|
|
|
|
|
- this.request.callsArgWith(1, null, { statusCode: 404 })
|
|
|
|
|
|
|
+ describe('when the file does not exist', function() {
|
|
|
|
|
+ let exists
|
|
|
|
|
|
|
|
- return this.S3PersistorManager.checkIfFileExists(
|
|
|
|
|
- this.bucketName,
|
|
|
|
|
- this.key,
|
|
|
|
|
- (err, exists) => {
|
|
|
|
|
- exists.should.equal(false)
|
|
|
|
|
- return done()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.headObject = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().rejects(S3NotFoundError) })
|
|
|
|
|
+ exists = await S3PersistorManager.promises.checkIfFileExists(
|
|
|
|
|
+ bucket,
|
|
|
|
|
+ key
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- return it('should return the error', function(done) {
|
|
|
|
|
- this.request.callsArgWith(1, this.error, {})
|
|
|
|
|
|
|
+ it('should get the object header', function() {
|
|
|
|
|
+ expect(S3Client.headObject).to.have.been.calledWith({
|
|
|
|
|
+ Bucket: bucket,
|
|
|
|
|
+ Key: key
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- return this.S3PersistorManager.checkIfFileExists(
|
|
|
|
|
- this.bucketName,
|
|
|
|
|
- this.key,
|
|
|
|
|
- err => {
|
|
|
|
|
- err.should.equal(this.error)
|
|
|
|
|
- return done()
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ it('should return that the file does not exist', function() {
|
|
|
|
|
+ expect(exists).to.equal(false)
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
- })
|
|
|
|
|
|
|
|
|
|
- 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()
|
|
|
|
|
|
|
+ describe('when there is an error', function() {
|
|
|
|
|
+ let error
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(async function() {
|
|
|
|
|
+ S3Client.headObject = sinon
|
|
|
|
|
+ .stub()
|
|
|
|
|
+ .returns({ promise: sinon.stub().rejects(genericError) })
|
|
|
|
|
+ try {
|
|
|
|
|
+ await S3PersistorManager.promises.checkIfFileExists(bucket, key)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ error = err
|
|
|
}
|
|
}
|
|
|
- )
|
|
|
|
|
- }))
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should generate a ReadError', function() {
|
|
|
|
|
+ expect(error).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should wrap the upstream ReadError', function() {
|
|
|
|
|
+ expect(error.cause).to.be.an.instanceOf(Errors.ReadError)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ it('should eventually wrap the error', function() {
|
|
|
|
|
+ expect(error.cause.cause).to.equal(genericError)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|