/* eslint-disable
    no-return-assign,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
require('chai').should()
const modulePath = require('path').join(
  __dirname,
  '../../../app/js/DockerLockManager'
)

describe('LockManager', function () {
  beforeEach(function () {
    return (this.LockManager = SandboxedModule.require(modulePath, {
      requires: {
        'settings-sharelatex': (this.Settings = { clsi: { docker: {} } }),
        'logger-sharelatex': (this.logger = {
          log: sinon.stub(),
          error: sinon.stub()
        })
      }
    }))
  })

  return describe('runWithLock', function () {
    describe('with a single lock', function () {
      beforeEach(function (done) {
        this.callback = sinon.stub()
        return this.LockManager.runWithLock(
          'lock-one',
          (releaseLock) =>
            setTimeout(() => releaseLock(null, 'hello', 'world'), 100),

          (err, ...args) => {
            this.callback(err, ...Array.from(args))
            return done()
          }
        )
      })

      return it('should call the callback', function () {
        return this.callback
          .calledWith(null, 'hello', 'world')
          .should.equal(true)
      })
    })

    describe('with two locks', function () {
      beforeEach(function (done) {
        this.callback1 = sinon.stub()
        this.callback2 = sinon.stub()
        this.LockManager.runWithLock(
          'lock-one',
          (releaseLock) =>
            setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100),

          (err, ...args) => {
            return this.callback1(err, ...Array.from(args))
          }
        )
        return this.LockManager.runWithLock(
          'lock-two',
          (releaseLock) =>
            setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200),

          (err, ...args) => {
            this.callback2(err, ...Array.from(args))
            return done()
          }
        )
      })

      it('should call the first callback', function () {
        return this.callback1
          .calledWith(null, 'hello', 'world', 'one')
          .should.equal(true)
      })

      return it('should call the second callback', function () {
        return this.callback2
          .calledWith(null, 'hello', 'world', 'two')
          .should.equal(true)
      })
    })

    return describe('with lock contention', function () {
      describe('where the first lock is released quickly', function () {
        beforeEach(function (done) {
          this.LockManager.MAX_LOCK_WAIT_TIME = 1000
          this.LockManager.LOCK_TEST_INTERVAL = 100
          this.callback1 = sinon.stub()
          this.callback2 = sinon.stub()
          this.LockManager.runWithLock(
            'lock',
            (releaseLock) =>
              setTimeout(() => releaseLock(null, 'hello', 'world', 'one'), 100),

            (err, ...args) => {
              return this.callback1(err, ...Array.from(args))
            }
          )
          return this.LockManager.runWithLock(
            'lock',
            (releaseLock) =>
              setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 200),

            (err, ...args) => {
              this.callback2(err, ...Array.from(args))
              return done()
            }
          )
        })

        it('should call the first callback', function () {
          return this.callback1
            .calledWith(null, 'hello', 'world', 'one')
            .should.equal(true)
        })

        return it('should call the second callback', function () {
          return this.callback2
            .calledWith(null, 'hello', 'world', 'two')
            .should.equal(true)
        })
      })

      describe('where the first lock is held longer than the waiting time', function () {
        beforeEach(function (done) {
          let doneTwo
          this.LockManager.MAX_LOCK_HOLD_TIME = 10000
          this.LockManager.MAX_LOCK_WAIT_TIME = 1000
          this.LockManager.LOCK_TEST_INTERVAL = 100
          this.callback1 = sinon.stub()
          this.callback2 = sinon.stub()
          let doneOne = (doneTwo = false)
          const finish = function (key) {
            if (key === 1) {
              doneOne = true
            }
            if (key === 2) {
              doneTwo = true
            }
            if (doneOne && doneTwo) {
              return done()
            }
          }
          this.LockManager.runWithLock(
            'lock',
            (releaseLock) =>
              setTimeout(
                () => releaseLock(null, 'hello', 'world', 'one'),
                1100
              ),

            (err, ...args) => {
              this.callback1(err, ...Array.from(args))
              return finish(1)
            }
          )
          return this.LockManager.runWithLock(
            'lock',
            (releaseLock) =>
              setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100),

            (err, ...args) => {
              this.callback2(err, ...Array.from(args))
              return finish(2)
            }
          )
        })

        it('should call the first callback', function () {
          return this.callback1
            .calledWith(null, 'hello', 'world', 'one')
            .should.equal(true)
        })

        return it('should call the second callback with an error', function () {
          const error = sinon.match.instanceOf(Error)
          return this.callback2.calledWith(error).should.equal(true)
        })
      })

      return describe('where the first lock is held longer than the max holding time', function () {
        beforeEach(function (done) {
          let doneTwo
          this.LockManager.MAX_LOCK_HOLD_TIME = 1000
          this.LockManager.MAX_LOCK_WAIT_TIME = 2000
          this.LockManager.LOCK_TEST_INTERVAL = 100
          this.callback1 = sinon.stub()
          this.callback2 = sinon.stub()
          let doneOne = (doneTwo = false)
          const finish = function (key) {
            if (key === 1) {
              doneOne = true
            }
            if (key === 2) {
              doneTwo = true
            }
            if (doneOne && doneTwo) {
              return done()
            }
          }
          this.LockManager.runWithLock(
            'lock',
            (releaseLock) =>
              setTimeout(
                () => releaseLock(null, 'hello', 'world', 'one'),
                1500
              ),

            (err, ...args) => {
              this.callback1(err, ...Array.from(args))
              return finish(1)
            }
          )
          return this.LockManager.runWithLock(
            'lock',
            (releaseLock) =>
              setTimeout(() => releaseLock(null, 'hello', 'world', 'two'), 100),

            (err, ...args) => {
              this.callback2(err, ...Array.from(args))
              return finish(2)
            }
          )
        })

        it('should call the first callback', function () {
          return this.callback1
            .calledWith(null, 'hello', 'world', 'one')
            .should.equal(true)
        })

        return it('should call the second callback', function () {
          return this.callback2
            .calledWith(null, 'hello', 'world', 'two')
            .should.equal(true)
        })
      })
    })
  })
})
