/* * 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 chai = require('chai') const { expect } = chai const path = require('path') const modulePath = path.join(__dirname, '../../../timeAsyncMethod.js') const SandboxedModule = require('sandboxed-module') const sinon = require('sinon') describe('timeAsyncMethod', function () { beforeEach(function () { this.Timer = { done: sinon.stub() } this.TimerConstructor = sinon.stub().returns(this.Timer) this.metrics = { Timer: this.TimerConstructor, inc: sinon.stub(), } this.timeAsyncMethod = SandboxedModule.require(modulePath, { requires: { './index': this.metrics, }, }) return (this.testObject = { nextNumber(n, callback) { return setTimeout(() => callback(null, n + 1), 100) }, }) }) it('should have the testObject behave correctly before wrapping', function (done) { return this.testObject.nextNumber(2, (err, result) => { expect(err).to.not.exist expect(result).to.equal(3) return done() }) }) it('should wrap method without error', function (done) { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) return done() }) it('should transparently wrap method invocation in timer', function (done) { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) return this.testObject.nextNumber(2, (err, result) => { expect(err).to.not.exist expect(result).to.equal(3) expect(this.TimerConstructor.callCount).to.equal(1) expect(this.Timer.done.callCount).to.equal(1) return done() }) }) it('should increment success count', function (done) { this.metrics.inc = sinon.stub() this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) return this.testObject.nextNumber(2, (err, result) => { if (err) { return done(err) } expect(this.metrics.inc.callCount).to.equal(1) expect( this.metrics.inc.calledWith('someContext_result', 1, { method: 'TestObject_nextNumber', status: 'success', }) ).to.equal(true) return done() }) }) describe('when base method produces an error', function () { beforeEach(function () { this.metrics.inc = sinon.stub() return (this.testObject.nextNumber = function (n, callback) { return setTimeout(() => callback(new Error('woops')), 100) }) }) it('should propagate the error transparently', function (done) { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) return this.testObject.nextNumber(2, (err, result) => { expect(err).to.exist expect(err).to.be.instanceof(Error) expect(result).to.not.exist return done() }) }) return it('should increment failure count', function (done) { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) return this.testObject.nextNumber(2, (err, result) => { expect(err).to.exist expect(this.metrics.inc.callCount).to.equal(1) expect( this.metrics.inc.calledWith('someContext_result', 1, { method: 'TestObject_nextNumber', status: 'failed', }) ).to.equal(true) return done() }) }) }) describe('when a logger is supplied', function () { beforeEach(function () { return (this.logger = { log: sinon.stub() }) }) return it('should also call logger.log', function (done) { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject', this.logger ) return this.testObject.nextNumber(2, (err, result) => { expect(err).to.not.exist expect(result).to.equal(3) expect(this.TimerConstructor.callCount).to.equal(1) expect(this.Timer.done.callCount).to.equal(1) expect(this.logger.log.callCount).to.equal(1) return done() }) }) }) describe('when the wrapper cannot be applied', function () { beforeEach(function () {}) return it('should raise an error', function () { const badWrap = () => { return this.timeAsyncMethod( this.testObject, 'DEFINITELY_NOT_A_REAL_METHOD', 'someContext.TestObject' ) } return expect(badWrap).to.throw( /^.*expected object property 'DEFINITELY_NOT_A_REAL_METHOD' to be a function.*$/ ) }) }) return describe('when the wrapped function is not using a callback', function () { beforeEach(function () { this.realMethod = sinon.stub().returns(42) return (this.testObject.nextNumber = this.realMethod) }) it('should not throw an error', function () { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) const badCall = () => { return this.testObject.nextNumber(2) } return expect(badCall).to.not.throw(Error) }) return it('should call the underlying method', function () { this.timeAsyncMethod( this.testObject, 'nextNumber', 'someContext.TestObject' ) const result = this.testObject.nextNumber(12) expect(this.realMethod.callCount).to.equal(1) expect(this.realMethod.calledWith(12)).to.equal(true) return expect(result).to.equal(42) }) }) })