<commit_before>'use strict'

let co = require('co')
let lab = exports.lab = require('lab').script()
let expect = require('code').expect

let db = require('../db')
let customDb = require('../fixtures/custom-db')
let Comment = db.bookshelf.model('Comment')
let User = db.bookshelf.model('User')

lab.experiment('general tests', () => {
  lab.beforeEach(co.wrap(function * () {
    yield db.reset()
    yield db.knex.seed.run()
  }))

  lab.test('should work', co.wrap(function * () {
    let model = yield Comment.forge({ id: 1 }).destroy()

    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment).to.be.null()

    comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0].deleted_at).to.be.a.number()
    expect(model.get('deleted_at')).to.be.a.date()
  }))

  lab.test('should work with transactions', co.wrap(function * () {
    let err = yield db.bookshelf.transaction((transacting) => {
      return Comment.forge({ id: 1 })
      .destroy({ transacting })
      .then(() => { throw new Error('Rollback this transaction') })
    })
    .catch((err) => err)

    expect(err.message).to.equal('Rollback this transaction')

    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment.get('deleted_at')).to.be.null()
  }))

  lab.test('should throw when required', co.wrap(function * () {
    let err = yield Comment.forge({ id: 12345 })
    .destroy({ require: true })
    .catch((err) => err)

    expect(err).to.be.an.error('No Rows Deleted')
  }))

  lab.test('should allow override when destroying', co.wrap(function * () {
    yield Comment.forge({ id: 1 }).destroy()

    let comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0].deleted_at).to.be.a.number()

    yield Comment.forge({ id: 1 }).destroy({ hardDelete: true })

    comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0]).to.be.undefined()
  }))

  lab.test('should properly update the document when using query', co.wrap(function * () {
    yield Comment.where({ id: 1 }).destroy()

    let comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0].deleted_at).to.be.a.number()

    yield Comment.forge({ id: 1 }).destroy({ hardDelete: true })

    comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0]).to.be.undefined()
  }))

  lab.test('should allow querying soft deleted models', co.wrap(function * () {
    yield Comment.forge({ id: 1 }).destroy()

    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment).to.be.null()

    comment = yield Comment.forge({ id: 1 }).fetch({ withDeleted: true })
    expect(comment.id).to.equal(1)
    expect(comment.get('deleted_at')).to.be.a.number()
  }))

  lab.test('should correctly emit events', co.wrap(function * () {
    let events = []
    let model = Comment.forge({ id: 1 })

    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy({ customOption: true })

    expect(events).to.have.length(2)
    expect(events[0][0]).to.equal('destroying')
    expect(events[0][1].get('id')).to.equal(1)
    expect(events[0][2]).to.deep.contain({
      softDelete: true,
      customOption: true
    })
    expect(events[1][0]).to.equal('destroyed')
    expect(events[1][1].get('id')).to.equal(1)
    expect(events[1][2]).to.deep.contain({
      softDelete: true,
      customOption: true
    })
  }))

  lab.test('should hard delete when models do not have soft delete configured', co.wrap(function * () {
    yield User.forge({ id: 1 }).destroy()

    expect(yield db.knex('users').select('*').where('id', 1)).to.have.length(0)
  }))

  lab.test('should allow overriding the field name', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), { field: 'deleted' })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    yield Model.forge({ id: 1 }).destroy()

    // Try to fetch it trough the model
    let test = yield Model.forge({ id: 1 }).fetch()
    expect(test).to.be.null()

    // Now try to fetch it directly though knex
    test = yield db.knex('test').select('*').where('id', 1)
    expect(test[0].deleted).to.be.a.number()
  }))

  lab.test('should allow overriding the destroyed event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: { destroyed: false }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0]).to.equal('destroying')
  }))

  lab.test('should allow overriding the destroying event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: { destroying: false }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0]).to.equal('destroyed')
  }))

  lab.test('should allow overriding the saving event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroying: false,
          updating: false,
          saving: true
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('saving', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1].deleted).to.be.a.date()
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow overriding the updating event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroying: false,
          updating: true,
          saving: true
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('updating', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1].deleted).to.be.a.date()
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow overriding the saved event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroyed: false,
          updated: false,
          saved: true
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('saved', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1]).to.equal(1)
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow overriding the updated event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroyed: false,
          updated: true,
          saved: false
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('updated', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1]).to.equal(1)
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow disabling events', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: false
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy()

    expect(events).to.have.length(0)
  }))
})

<commit_msg>Add regression tests demonstrating query object being dropped<commit_after>'use strict'

let co = require('co')
let lab = exports.lab = require('lab').script()
let expect = require('code').expect

let db = require('../db')
let customDb = require('../fixtures/custom-db')
let Comment = db.bookshelf.model('Comment')
let User = db.bookshelf.model('User')

lab.experiment('general tests', () => {
  lab.beforeEach(co.wrap(function * () {
    yield db.reset()
    yield db.knex.seed.run()
  }))

  lab.test('should work', co.wrap(function * () {
    let model = yield Comment.forge({ id: 1 }).destroy()

    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment).to.be.null()

    comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0].deleted_at).to.be.a.number()
    expect(model.get('deleted_at')).to.be.a.date()
  }))

  lab.test('should work with transactions', co.wrap(function * () {
    let err = yield db.bookshelf.transaction((transacting) => {
      return Comment.forge({ id: 1 })
      .destroy({ transacting })
      .then(() => { throw new Error('Rollback this transaction') })
    })
    .catch((err) => err)

    expect(err.message).to.equal('Rollback this transaction')

    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment.get('deleted_at')).to.be.null()
  }))

  lab.test('should throw when required', co.wrap(function * () {
    let err = yield Comment.forge({ id: 12345 })
    .destroy({ require: true })
    .catch((err) => err)

    expect(err).to.be.an.error('No Rows Deleted')
  }))

  lab.test('should preserve original query object', co.wrap(function * () {
    yield Comment.forge({ article_id: 1 }).query((qb) => qb.where('id', 1)).destroy()
    let comment1 = yield Comment.forge({ id: 1 }).fetch()
    let comment2 = yield Comment.forge({ id: 2 }).fetch()
    expect(comment1).to.be.null()
    expect(comment2).to.not.be.null()
  }))

  lab.test('should delete according to query object', co.wrap(function * () {
    yield Comment.query((qb) => qb.where('id', 2)).destroy()
    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment).to.not.be.null()
  }))

  lab.test('should allow override when destroying', co.wrap(function * () {
    yield Comment.forge({ id: 1 }).destroy()

    let comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0].deleted_at).to.be.a.number()

    yield Comment.forge({ id: 1 }).destroy({ hardDelete: true })

    comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0]).to.be.undefined()
  }))

  lab.test('should properly update the document when using query', co.wrap(function * () {
    yield Comment.where({ id: 1 }).destroy()

    let comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0].deleted_at).to.be.a.number()

    yield Comment.forge({ id: 1 }).destroy({ hardDelete: true })

    comment = yield db.knex('comments').select('*').where('id', 1)
    expect(comment[0]).to.be.undefined()
  }))

  lab.test('should allow querying soft deleted models', co.wrap(function * () {
    yield Comment.forge({ id: 1 }).destroy()

    let comment = yield Comment.forge({ id: 1 }).fetch()
    expect(comment).to.be.null()

    comment = yield Comment.forge({ id: 1 }).fetch({ withDeleted: true })
    expect(comment.id).to.equal(1)
    expect(comment.get('deleted_at')).to.be.a.number()
  }))

  lab.test('should correctly emit events', co.wrap(function * () {
    let events = []
    let model = Comment.forge({ id: 1 })

    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy({ customOption: true })

    expect(events).to.have.length(2)
    expect(events[0][0]).to.equal('destroying')
    expect(events[0][1].get('id')).to.equal(1)
    expect(events[0][2]).to.deep.contain({
      softDelete: true,
      customOption: true
    })
    expect(events[1][0]).to.equal('destroyed')
    expect(events[1][1].get('id')).to.equal(1)
    expect(events[1][2]).to.deep.contain({
      softDelete: true,
      customOption: true
    })
  }))

  lab.test('should hard delete when models do not have soft delete configured', co.wrap(function * () {
    yield User.forge({ id: 1 }).destroy()

    expect(yield db.knex('users').select('*').where('id', 1)).to.have.length(0)
  }))

  lab.test('should allow overriding the field name', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), { field: 'deleted' })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    yield Model.forge({ id: 1 }).destroy()

    // Try to fetch it trough the model
    let test = yield Model.forge({ id: 1 }).fetch()
    expect(test).to.be.null()

    // Now try to fetch it directly though knex
    test = yield db.knex('test').select('*').where('id', 1)
    expect(test[0].deleted).to.be.a.number()
  }))

  lab.test('should allow overriding the destroyed event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: { destroyed: false }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0]).to.equal('destroying')
  }))

  lab.test('should allow overriding the destroying event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: { destroying: false }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0]).to.equal('destroyed')
  }))

  lab.test('should allow overriding the saving event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroying: false,
          updating: false,
          saving: true
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('saving', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1].deleted).to.be.a.date()
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow overriding the updating event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroying: false,
          updating: true,
          saving: true
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('updating', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1].deleted).to.be.a.date()
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow overriding the saved event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroyed: false,
          updated: false,
          saved: true
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('saved', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1]).to.equal(1)
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow overriding the updated event', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: {
          destroyed: false,
          updated: true,
          saved: false
        }
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('updated', (model, attrs, options) => events.push([model, attrs, options]))

    yield model.destroy()

    expect(events).to.have.length(1)
    expect(events[0][0].id).to.equal(1)
    expect(events[0][1]).to.equal(1)
    expect(events[0][2].previousAttributes.deleted).to.not.exist()
  }))

  lab.test('should allow disabling events', co.wrap(function * () {
    // Create a new bookshelf instance
    let bookshelf = yield customDb.altFieldTable((bookshelf) => {
      bookshelf.plugin(require('../../'), {
        field: 'deleted',
        events: false
      })
    })

    // Create the model that corresponds to our newly created table
    let Model = bookshelf.Model.extend({ tableName: 'test', softDelete: true })
    let events = []

    let model = Model.forge({ id: 1 })
    model.on('destroying', (model, options) => events.push(['destroying', model, options]))
    model.on('destroyed', (model, options) => events.push(['destroyed', model, options]))

    yield model.destroy()

    expect(events).to.have.length(0)
  }))
})
<|endoftext|>


<commit_before>/* global sinon, expect, request, describe, it, before, after */
import '../setup'
import { init, config, log, service, setCORS, procResponse, buildConfig, setRunner, parseRequest } from '../../src/app'

const url = 'http://localhost:8181/api/'

describe('app', () => {

  describe('config', () => {

    it('overrides defaults with passed object', () => {
      const testConfig = {
        lambdas: 'test-override',
        schema: 'gateway-override',
        port: 1111,
        apiPath: '/test-override',
        log: false,
        cors: {
          origin: '*',
          methods: 'GET,PUT,POST,DELETE,OPTIONS',
          headers: 'Content-Type, Authorization, Content-Length, X-Requested-With'
        }
      }
      buildConfig(testConfig)
      expect(config).to.deep.equal(testConfig)
    })

    it('overvides config with environment variables', () => {
      process.env.GL_LAMBDAS='test-env'
      process.env.GL_SCHEMA='gateway-env'
      process.env.GL_PORT='2222'
      process.env.GL_APIPATH='/test-env'
      buildConfig()
      expect(config).to.deep.equal({
        lambdas: 'test-env',
        schema: 'gateway-env',
        port: '2222',
        apiPath: '/test-env',
        log: false,
        cors: {
          origin: '*',
          methods: 'GET,PUT,POST,DELETE,OPTIONS',
          headers: 'Content-Type, Authorization, Content-Length, X-Requested-With'
        }
      })
    })

    after(() => {
      delete process.env.GL_LAMBDAS
      delete process.env.GL_SCHEMA
      delete process.env.GL_PORT
      delete process.env.GL_APIPATH
    })

  })

  describe('setCORS', () => {
    it('sets CORS properties on the service object', () => {
      config.cors.methods = 'GET,PUT,POST,DELETE,OPTIONS,SCAN'
      setCORS();
      service.get('test', (req, res) => {
        console.log(arguments);
      })
    })
  })

  describe('procResponse', () => {

    let resStub = {
      status: function (code) {
        return {
          send: function (msg) {
            return { code: code, msg: msg }
          }
        }
      }
    }

    let responseSpy = sinon.spy(resStub, 'status')

    it('logs info on metrics case', () => {
      procResponse({ type: 'metric', output: 'test' })
      sinon.mock(log).expects('info').once
    })

    it('logs error on default case', () => {
      procResponse({ type: null, output: 'no type'})
      sinon.mock(log).expects('error').once
    })

    it('responds on success case', () => {
      procResponse({ type: 'success', output: 'response' }, resStub)
      expect(responseSpy).to.have.been.called
    })

    it('responds on error case', () => {
      procResponse({ type: 'error', output: 'response' }, resStub)
      expect(responseSpy).to.have.been.called
    })

  })

  describe('parseBody', () => {

    it('combines custom params and req.body to create event object', () => {
      const req = { body: { foo: 'bar' } }
      const template = { baz: 'quz', body: `$input.json('$')` }
      const event = parseRequest(req, template)
      const shouldBe = { baz: 'quz', body: { foo: 'bar' } }
      expect(event).to.deep.equal(shouldBe)
    })

  })

  describe('requests', () => {

    before(() => {
      // Set runner
      setRunner('./build/runner')
      // Start app
      init({
        port: 8181,
        lambdas: './test/lambdas',
        schema: './test/gateway.yml',
        apiPath: '/api',
        log: false
      })
    })

    it('responds with the correct method and request params', (done) => {
      request(url)
        .get('foo/someId')
        .expect(200)
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          res.body.should.have.property('method')
          res.body.method.should.equal('get')
          res.body.should.have.property('id')
          res.body.id.should.equal('someId')
          done()
        })
    })

    it('responds with correct header values', (done) => {
      request(url)
        .get('foo')
        .set('Content-Type', 'application/json')
        .expect(200)
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          res.body.should.have.property('contentType')
          res.body.contentType.should.equal('application/json')
          done()
        })
    })

    it('responds with the correct querystring values', (done) => {
      request(url)
        .get('foo?querytest=bar')
        .expect(200)
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          res.body.should.have.property('queryTest')
          res.body.queryTest.should.equal('bar')
          done()
        })
    })

    it('responds with correct event property values', (done) => {
      request(url)
        .put('foo/someId')
        .send({ foo: 'bar' })
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          let responseText = res.body
          // Custom pass-through
          responseText.should.have.property('baz')
          responseText.baz.should.equal('quz')
          // Body pass-through
          responseText.should.have.property('body')
          responseText.body.should.have.property('foo')
          responseText.body.foo.should.equal('bar')
          done()
        })
    })

    it('responds with a 500 on fail', (done) => {
      request(url)
        .put('foo/someId')
        .send({ failTest: true })
        .expect(500)
        .end(() => {
          done()
        })
    })

  })

})<commit_msg>Remove bs testing, just run test to ensure no errors thrown<commit_after>/* global sinon, expect, request, describe, it, before, after */
import '../setup'
import { init, config, log, service, setCORS, procResponse, buildConfig, setRunner, parseRequest } from '../../src/app'

const url = 'http://localhost:8181/api/'

describe('app', () => {

  describe('config', () => {

    it('overrides defaults with passed object', () => {
      const testConfig = {
        lambdas: 'test-override',
        schema: 'gateway-override',
        port: 1111,
        apiPath: '/test-override',
        log: false,
        cors: {
          origin: '*',
          methods: 'GET,PUT,POST,DELETE,OPTIONS',
          headers: 'Content-Type, Authorization, Content-Length, X-Requested-With'
        }
      }
      buildConfig(testConfig)
      expect(config).to.deep.equal(testConfig)
    })

    it('overvides config with environment variables', () => {
      process.env.GL_LAMBDAS='test-env'
      process.env.GL_SCHEMA='gateway-env'
      process.env.GL_PORT='2222'
      process.env.GL_APIPATH='/test-env'
      buildConfig()
      expect(config).to.deep.equal({
        lambdas: 'test-env',
        schema: 'gateway-env',
        port: '2222',
        apiPath: '/test-env',
        log: false,
        cors: {
          origin: '*',
          methods: 'GET,PUT,POST,DELETE,OPTIONS',
          headers: 'Content-Type, Authorization, Content-Length, X-Requested-With'
        }
      })
    })

    after(() => {
      delete process.env.GL_LAMBDAS
      delete process.env.GL_SCHEMA
      delete process.env.GL_PORT
      delete process.env.GL_APIPATH
    })

  })

  describe('setCORS', () => {
    it('sets CORS properties on the service object', () => {
      config.cors.methods = 'GET,PUT,POST,DELETE,OPTIONS,SCAN'
      setCORS();
    })
  })

  describe('procResponse', () => {

    let resStub = {
      status: function (code) {
        return {
          send: function (msg) {
            return { code: code, msg: msg }
          }
        }
      }
    }

    let responseSpy = sinon.spy(resStub, 'status')

    it('logs info on metrics case', () => {
      procResponse({ type: 'metric', output: 'test' })
      sinon.mock(log).expects('info').once
    })

    it('logs error on default case', () => {
      procResponse({ type: null, output: 'no type'})
      sinon.mock(log).expects('error').once
    })

    it('responds on success case', () => {
      procResponse({ type: 'success', output: 'response' }, resStub)
      expect(responseSpy).to.have.been.called
    })

    it('responds on error case', () => {
      procResponse({ type: 'error', output: 'response' }, resStub)
      expect(responseSpy).to.have.been.called
    })

  })

  describe('parseBody', () => {

    it('combines custom params and req.body to create event object', () => {
      const req = { body: { foo: 'bar' } }
      const template = { baz: 'quz', body: `$input.json('$')` }
      const event = parseRequest(req, template)
      const shouldBe = { baz: 'quz', body: { foo: 'bar' } }
      expect(event).to.deep.equal(shouldBe)
    })

  })

  describe('requests', () => {

    before(() => {
      // Set runner
      setRunner('./build/runner')
      // Start app
      init({
        port: 8181,
        lambdas: './test/lambdas',
        schema: './test/gateway.yml',
        apiPath: '/api',
        log: false
      })
    })

    it('responds with the correct method and request params', (done) => {
      request(url)
        .get('foo/someId')
        .expect(200)
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          res.body.should.have.property('method')
          res.body.method.should.equal('get')
          res.body.should.have.property('id')
          res.body.id.should.equal('someId')
          done()
        })
    })

    it('responds with correct header values', (done) => {
      request(url)
        .get('foo')
        .set('Content-Type', 'application/json')
        .expect(200)
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          res.body.should.have.property('contentType')
          res.body.contentType.should.equal('application/json')
          done()
        })
    })

    it('responds with the correct querystring values', (done) => {
      request(url)
        .get('foo?querytest=bar')
        .expect(200)
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          res.body.should.have.property('queryTest')
          res.body.queryTest.should.equal('bar')
          done()
        })
    })

    it('responds with correct event property values', (done) => {
      request(url)
        .put('foo/someId')
        .send({ foo: 'bar' })
        .end((err, res) => {
          if (err) {
            done(err)
            return
          }
          let responseText = res.body
          // Custom pass-through
          responseText.should.have.property('baz')
          responseText.baz.should.equal('quz')
          // Body pass-through
          responseText.should.have.property('body')
          responseText.body.should.have.property('foo')
          responseText.body.foo.should.equal('bar')
          done()
        })
    })

    it('responds with a 500 on fail', (done) => {
      request(url)
        .put('foo/someId')
        .send({ failTest: true })
        .expect(500)
        .end(() => {
          done()
        })
    })

  })

})<|endoftext|>


<commit_before>export default function() {

  // These comments are here to help you get started. Feel free to delete them.

  /*
    Config (with defaults).

    Note: these only affect routes defined *after* them!
  */
  // this.urlPrefix = '';    // make this `http://localhost:8080`, for example, if your API is on a different server
  // this.namespace = '';    // make this `api`, for example, if your API is namespaced
  // this.timing = 400;      // delay for each request, automatically set to 0 during testing

  /*
    Route shorthand cheatsheet
  */
  /*
    GET shorthands

    // Collections
    this.get('/contacts');
    this.get('/contacts', 'users');
    this.get('/contacts', ['contacts', 'addresses']);

    // Single objects
    this.get('/contacts/:id');
    this.get('/contacts/:id', 'user');
    this.get('/contacts/:id', ['contact', 'addresses']);
  */

  /*
    POST shorthands

    this.post('/contacts');
    this.post('/contacts', 'user'); // specify the type of resource to be created
  */

  /*
    PUT shorthands

    this.put('/contacts/:id');
    this.put('/contacts/:id', 'user'); // specify the type of resource to be updated
  */

  /*
    DELETE shorthands

    this.del('/contacts/:id');
    this.del('/contacts/:id', 'user'); // specify the type of resource to be deleted

    // Single object + related resources. Make sure parent resource is first.
    this.del('/contacts/:id', ['contact', 'addresses']);
  */

  /*
    Function fallback. Manipulate data in the db via

      - db.{collection}
      - db.{collection}.find(id)
      - db.{collection}.where(query)
      - db.{collection}.update(target, attrs)
      - db.{collection}.remove(target)

    // Example: return a single object with related models
    this.get('/contacts/:id', function(db, request) {
      var contactId = +request.params.id;

      return {
        contact: db.contacts.find(contactId),
        addresses: db.addresses.where({contact_id: contactId})
      };
    });

  */
}

/*
You can optionally export a config that is only loaded during tests
export function testConfig() {

}
*/
<commit_msg>BM-306: Update mirage config<commit_after>/**
 * @file Specify API end-points.
 * Mirage wraps Pretender, which intercepts requests that would normally hit your API, and allows you to specify
 * the response that should be sent back.
 */
export default function () {
  /**
   * As Sails.js API is on a different server
   */
  this.urlPrefix = 'http://localhost:8080';

  /**
   * API is namespaced
   */
  this.namespace = 'api';

  /**
   * Timing parameter of the response.
   *  Default is a 400ms delay during development and 0 delay in testing (so your tests run fast).
   */
  this.timing = 400;

  /**
   * Setup endpoints for retrieving fake models
   *  Route is made dynamic by responding with all the centres in Mirage's in-memory database.
   */

  /**
   * Route will handle the URL '/api/centres'
   */
  this.get('/centres', function (db) {
    return {
      data: db.centres.map(attrs => (
      {type: 'centres', id: attrs.id, attributes: attrs}
      ))
    };
  });

  /**
   * Route will handle the URL '/api/centre/{id}'
   */
  this.get('/centres/:id', function (db, request) {
    let id = request.params.id;

    return {
      data: {
        type: 'centres',
        id: id,
        attributes: db.centres.find(id)
      }
    };
  });
}
<|endoftext|>


<commit_before>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Abstract ordered drawing surface. Each Layer contains a canvas element and
 * provides simple drawing instructions for drawing to that canvas element,
 * however unlike the canvas element itself, drawing operations on a Layer are
 * guaranteed to run in order, even if such an operation must wait for an image
 * to load before completing.
 *
 * @constructor
 *
 * @param {Number} width The width of the Layer, in pixels. The canvas element
 *                       backing this Layer will be given this width.
 *
 * @param {Number} height The height of the Layer, in pixels. The canvas element
 *                        backing this Layer will be given this height.
 */
Guacamole.Layer = function(width, height) {

    /**
     * Reference to this Layer.
     * @private
     */
    var layer = this;

    /**
     * The canvas element backing this Layer.
     * @private
     */
    var display = document.createElement("canvas");

    /**
     * The 2D display context of the canvas element backing this Layer.
     * @private
     */
    var displayContext = display.getContext("2d");
    displayContext.save();

    /**
     * The queue of all pending Tasks. Tasks will be run in order, with new
     * tasks added at the end of the queue and old tasks removed from the
     * front of the queue (FIFO).
     * @private
     */
    var tasks = new Array();

    /**
     * Map of all Guacamole channel masks to HTML5 canvas composite operation
     * names. Not all channel mask combinations are currently implemented.
     * @private
     */
    var compositeOperation = {
     /* 0x0 NOT IMPLEMENTED */
        0x1: "destination-in",
        0x2: "destination-out",
     /* 0x3 NOT IMPLEMENTED */
        0x4: "source-in",
     /* 0x5 NOT IMPLEMENTED */
        0x6: "source-atop",
     /* 0x7 NOT IMPLEMENTED */
        0x8: "source-out",
        0x9: "destination-atop",
        0xA: "xor",
        0xB: "destination-over",
        0xC: "copy",
     /* 0xD NOT IMPLEMENTED */
        0xE: "source-over",
        0xF: "lighter"
    };

    /**
     * Resizes the canvas element backing this Layer without testing the
     * new size. This function should only be used internally.
     *
     * @private
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    function resize(newWidth, newHeight) {
        display.width = newWidth;
        display.height = newHeight;

        width = newWidth;
        height = newHeight;
    }

    /**
     * Given the X and Y coordinates of the upper-left corner of a rectangle
     * and the rectangle's width and height, resize the backing canvas element
     * as necessary to ensure that the rectangle fits within the canvas
     * element's coordinate space. This function will only make the canvas
     * larger. If the rectangle already fits within the canvas element's
     * coordinate space, the canvas is left unchanged.
     *
     * @private
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} w The width of the the rectangle to fit.
     * @param {Number} h The height of the the rectangle to fit.
     */
    function fitRect(x, y, w, h) {

        // Calculate bounds
        var opBoundX = w + x;
        var opBoundY = h + y;

        // Determine max width
        var resizeWidth;
        if (opBoundX > width)
            resizeWidth = opBoundX;
        else
            resizeWidth = width;

        // Determine max height
        var resizeHeight;
        if (opBoundY > height)
            resizeHeight = opBoundY;
        else
            resizeHeight = height;

        // Resize if necessary
        if (resizeWidth != width || resizeHeight != height)
            resize(resizeWidth, resizeHeight);

    }

    /**
     * A container for an task handler. Each operation which must be ordered
     * is associated with a Task that goes into a task queue. Tasks in this
     * queue are executed in order once their handlers are set, while Tasks
     * without handlers block themselves and any following Tasks from running.
     *
     * @constructor
     * @private
     * @param {function} taskHandler The function to call when this task
     *                               runs, if any.
     */
    function Task(taskHandler) {

        /**
         * The handler this Task is associated with, if any.
         *
         * @type function
         */
        this.handler = taskHandler;

    }

    /**
     * If no tasks are pending or running, run the provided handler immediately,
     * if any. Otherwise, schedule a task to run immediately after all currently
     * running or pending tasks are complete.
     *
     * @private
     * @param {function} handler The function to call when possible, if any.
     * @returns {Task} The Task created and added to the queue for future
     *                 running, if any, or null if the handler was run
     *                 immediately and no Task needed to be created.
     */
    function scheduleTask(handler) {

        // If no pending tasks, just call (if available) and exit
        if (layer.isReady() && handler != null) {
            handler();
            return null;
        }

        // If tasks are pending/executing, schedule a pending task
        // and return a reference to it.
        var task = new Task(handler);
        tasks.push(task);
        return task;

    }

    /**
     * Run any Tasks which were pending but are now ready to run and are not
     * blocked by other Tasks.
     * @private
     */
    function handlePendingTasks() {

        // Draw all pending tasks.
        var task;
        while ((task = tasks[0]) != null && task.handler) {
            tasks.shift();
            task.handler();
        }

    }

    /**
     * Set to true if this Layer should resize itself to accomodate the
     * dimensions of any drawing operation, and false (the default) otherwise.
     *
     * Note that setting this property takes effect immediately, and thus may
     * take effect on operations that were started in the past but have not
     * yet completed. If you wish the setting of this flag to only modify
     * future operations, you will need to make the setting of this flag an
     * operation with sync().
     *
     * @example
     * // Set autosize to true for all future operations
     * layer.sync(function() {
     *     layer.autosize = true;
     * });
     *
     * @type Boolean
     * @default false
     */
    this.autosize = false;

    /**
     * Returns the canvas element backing this Layer.
     * @returns {Element} The canvas element backing this Layer.
     */
    this.getCanvas = function() {
        return display;
    };

    /**
     * Returns whether this Layer is ready. A Layer is ready if it has no
     * pending operations and no operations in-progress.
     *
     * @returns {Boolean} true if this Layer is ready, false otherwise.
     */
    this.isReady = function() {
        return tasks.length == 0;
    };

    /**
     * Changes the size of this Layer to the given width and height. Resizing
     * is only attempted if the new size provided is actually different from
     * the current size.
     *
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    this.resize = function(newWidth, newHeight) {
        scheduleTask(function() {
            if (newWidth != width || newHeight != height)
                resize(newWidth, newHeight);
        });
    };

    /**
     * Draws the specified image at the given coordinates. The image specified
     * must already be loaded.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {Image} image The image to draw. Note that this is an Image
     *                      object - not a URL.
     */
    this.drawImage = function(x, y, image) {
        scheduleTask(function() {
            if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
            displayContext.drawImage(image, x, y);
        });
    };

    /**
     * Draws the image at the specified URL at the given coordinates. The image
     * will be loaded automatically, and this and any future operations will
     * wait for the image to finish loading.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {String} url The URL of the image to draw.
     */
    this.draw = function(x, y, url) {
        var task = scheduleTask(null);

        var image = new Image();
        image.onload = function() {

            task.handler = function() {
                if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
                displayContext.drawImage(image, x, y);
            };

            // As this task originally had no handler and may have blocked
            // other tasks, handle any blocked tasks.
            handlePendingTasks();

        };
        image.src = url;

    };

    /**
     * Run an arbitrary function as soon as currently pending operations
     * are complete.
     *
     * @param {function} handler The function to call once all currently
     *                           pending operations are complete.
     */
    this.sync = function(handler) {
        scheduleTask(handler);
    };

    /**
     * Copy a rectangle of image data from one Layer to this Layer. This
     * operation will copy exactly the image data that will be drawn once all
     * operations of the source Layer that were pending at the time this
     * function was called are complete. This operation will not alter the
     * size of the source Layer even if its autosize property is set to true.
     *
     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
     * @param {Number} srcx The X coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcy The Y coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcw The width of the rectangle within the source Layer's
     *                      coordinate space to copy data from.
     * @param {Number} srch The height of the rectangle within the source
     *                      Layer's coordinate space to copy data from.
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     */
    this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) {

        function doCopyRect() {
            if (layer.autosize != 0) fitRect(x, y, srcw, srch);
            displayContext.drawImage(srcLayer.getCanvas(), srcx, srcy, srcw, srch, x, y, srcw, srch);
        }

        // If we ARE the source layer, no need to sync.
        // Syncing would result in deadlock.
        if (layer === srcLayer)
            scheduleTask(doCopyRect);

        // Otherwise synchronize copy operation with source layer
        else {
            var task = scheduleTask(null);
            srcLayer.sync(function() {

                task.handler = doCopyRect;

                // As this task originally had no handler and may have blocked
                // other tasks, handle any blocked tasks.
                handlePendingTasks();

            });
        }

    };

    /**
     * Clear the specified rectangle of image data.
     *
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to clear.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to clear.
     * @param {Number} w The width of the rectangle to clear.
     * @param {Number} h The height of the rectangle to clear.
     */
    this.clearRect = function(x, y, w, h) {
        scheduleTask(function() {
            if (layer.autosize != 0) fitRect(x, y, w, h);
            displayContext.clearRect(x, y, w, h);
        });
    };

    /**
     * Fill the specified rectangle of image data with the specified color.
     *
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to draw.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to draw.
     * @param {Number} w The width of the rectangle to draw.
     * @param {Number} h The height of the rectangle to draw.
     * @param {Number} r The red component of the color of the rectangle.
     * @param {Number} g The green component of the color of the rectangle.
     * @param {Number} b The blue component of the color of the rectangle.
     * @param {Number} a The alpha component of the color of the rectangle.
     */
    this.drawRect = function(x, y, w, h, r, g, b, a) {
        scheduleTask(function() {
            if (layer.autosize != 0) fitRect(x, y, w, h);
            displayContext.fillStyle = "rgba("
                        + r + "," + g + "," + b + "," + a + ")";
            displayContext.fillRect(x, y, w, h);
        });
    };

    /**
     * Clip all future drawing operations by the specified rectangle.
     *
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to use for the clipping region.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to use for the clipping region.
     * @param {Number} w The width of the rectangle to use for the clipping region.
     * @param {Number} h The height of the rectangle to use for the clipping region.
     */
    this.clipRect = function(x, y, w, h) {
        scheduleTask(function() {

            // Clear any current clipping region
            displayContext.restore();
            displayContext.save();

            if (layer.autosize != 0) fitRect(x, y, w, h);

            // Set new clipping region
            displayContext.beginPath();
            displayContext.rect(x, y, w, h);
            displayContext.clip();

        });
    };

    /**
     * Provides the given filtering function with a writable snapshot of
     * image data and the current width and height of the Layer.
     *
     * @param {function} filter A function which accepts an array of image
     *                          data (as returned by the canvas element's
     *                          display context's getImageData() function),
     *                          the width of the Layer, and the height of the
     *                          Layer as parameters, in that order. This
     *                          function must accomplish its filtering by
     *                          modifying the given image data array directly.
     */
    this.filter = function(filter) {
        scheduleTask(function() {
            var imageData = displayContext.getImageData(0, 0, width, height);
            filter(imageData.data, width, height);
            displayContext.putImageData(imageData, 0, 0);
        });
    };

    /**
     * Sets the channel mask for future operations on this Layer. The channel
     * mask is a Guacamole-specific compositing operation identifier with a
     * single bit representing each of four channels (in order): source image
     * where destination transparent, source where destination opaque,
     * destination where source transparent, and destination where source
     * opaque.
     *
     * @param {Number} mask The channel mask for future operations on this
     *                      Layer.
     */
    this.setChannelMask = function(mask) {
        scheduleTask(function() {
            displayContext.globalCompositeOperation = compositeOperation[mask];
        });
    };

    // Initialize canvas dimensions
    resize(width, height);

};
<commit_msg>Alpha for rgba() is from 0.0 to 1.0, not 0 to 255.<commit_after>
/*
 *  Guacamole - Clientless Remote Desktop
 *  Copyright (C) 2010  Michael Jumper
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Abstract ordered drawing surface. Each Layer contains a canvas element and
 * provides simple drawing instructions for drawing to that canvas element,
 * however unlike the canvas element itself, drawing operations on a Layer are
 * guaranteed to run in order, even if such an operation must wait for an image
 * to load before completing.
 *
 * @constructor
 *
 * @param {Number} width The width of the Layer, in pixels. The canvas element
 *                       backing this Layer will be given this width.
 *
 * @param {Number} height The height of the Layer, in pixels. The canvas element
 *                        backing this Layer will be given this height.
 */
Guacamole.Layer = function(width, height) {

    /**
     * Reference to this Layer.
     * @private
     */
    var layer = this;

    /**
     * The canvas element backing this Layer.
     * @private
     */
    var display = document.createElement("canvas");

    /**
     * The 2D display context of the canvas element backing this Layer.
     * @private
     */
    var displayContext = display.getContext("2d");
    displayContext.save();

    /**
     * The queue of all pending Tasks. Tasks will be run in order, with new
     * tasks added at the end of the queue and old tasks removed from the
     * front of the queue (FIFO).
     * @private
     */
    var tasks = new Array();

    /**
     * Map of all Guacamole channel masks to HTML5 canvas composite operation
     * names. Not all channel mask combinations are currently implemented.
     * @private
     */
    var compositeOperation = {
     /* 0x0 NOT IMPLEMENTED */
        0x1: "destination-in",
        0x2: "destination-out",
     /* 0x3 NOT IMPLEMENTED */
        0x4: "source-in",
     /* 0x5 NOT IMPLEMENTED */
        0x6: "source-atop",
     /* 0x7 NOT IMPLEMENTED */
        0x8: "source-out",
        0x9: "destination-atop",
        0xA: "xor",
        0xB: "destination-over",
        0xC: "copy",
     /* 0xD NOT IMPLEMENTED */
        0xE: "source-over",
        0xF: "lighter"
    };

    /**
     * Resizes the canvas element backing this Layer without testing the
     * new size. This function should only be used internally.
     *
     * @private
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    function resize(newWidth, newHeight) {
        display.width = newWidth;
        display.height = newHeight;

        width = newWidth;
        height = newHeight;
    }

    /**
     * Given the X and Y coordinates of the upper-left corner of a rectangle
     * and the rectangle's width and height, resize the backing canvas element
     * as necessary to ensure that the rectangle fits within the canvas
     * element's coordinate space. This function will only make the canvas
     * larger. If the rectangle already fits within the canvas element's
     * coordinate space, the canvas is left unchanged.
     *
     * @private
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to fit.
     * @param {Number} w The width of the the rectangle to fit.
     * @param {Number} h The height of the the rectangle to fit.
     */
    function fitRect(x, y, w, h) {

        // Calculate bounds
        var opBoundX = w + x;
        var opBoundY = h + y;

        // Determine max width
        var resizeWidth;
        if (opBoundX > width)
            resizeWidth = opBoundX;
        else
            resizeWidth = width;

        // Determine max height
        var resizeHeight;
        if (opBoundY > height)
            resizeHeight = opBoundY;
        else
            resizeHeight = height;

        // Resize if necessary
        if (resizeWidth != width || resizeHeight != height)
            resize(resizeWidth, resizeHeight);

    }

    /**
     * A container for an task handler. Each operation which must be ordered
     * is associated with a Task that goes into a task queue. Tasks in this
     * queue are executed in order once their handlers are set, while Tasks
     * without handlers block themselves and any following Tasks from running.
     *
     * @constructor
     * @private
     * @param {function} taskHandler The function to call when this task
     *                               runs, if any.
     */
    function Task(taskHandler) {

        /**
         * The handler this Task is associated with, if any.
         *
         * @type function
         */
        this.handler = taskHandler;

    }

    /**
     * If no tasks are pending or running, run the provided handler immediately,
     * if any. Otherwise, schedule a task to run immediately after all currently
     * running or pending tasks are complete.
     *
     * @private
     * @param {function} handler The function to call when possible, if any.
     * @returns {Task} The Task created and added to the queue for future
     *                 running, if any, or null if the handler was run
     *                 immediately and no Task needed to be created.
     */
    function scheduleTask(handler) {

        // If no pending tasks, just call (if available) and exit
        if (layer.isReady() && handler != null) {
            handler();
            return null;
        }

        // If tasks are pending/executing, schedule a pending task
        // and return a reference to it.
        var task = new Task(handler);
        tasks.push(task);
        return task;

    }

    /**
     * Run any Tasks which were pending but are now ready to run and are not
     * blocked by other Tasks.
     * @private
     */
    function handlePendingTasks() {

        // Draw all pending tasks.
        var task;
        while ((task = tasks[0]) != null && task.handler) {
            tasks.shift();
            task.handler();
        }

    }

    /**
     * Set to true if this Layer should resize itself to accomodate the
     * dimensions of any drawing operation, and false (the default) otherwise.
     *
     * Note that setting this property takes effect immediately, and thus may
     * take effect on operations that were started in the past but have not
     * yet completed. If you wish the setting of this flag to only modify
     * future operations, you will need to make the setting of this flag an
     * operation with sync().
     *
     * @example
     * // Set autosize to true for all future operations
     * layer.sync(function() {
     *     layer.autosize = true;
     * });
     *
     * @type Boolean
     * @default false
     */
    this.autosize = false;

    /**
     * Returns the canvas element backing this Layer.
     * @returns {Element} The canvas element backing this Layer.
     */
    this.getCanvas = function() {
        return display;
    };

    /**
     * Returns whether this Layer is ready. A Layer is ready if it has no
     * pending operations and no operations in-progress.
     *
     * @returns {Boolean} true if this Layer is ready, false otherwise.
     */
    this.isReady = function() {
        return tasks.length == 0;
    };

    /**
     * Changes the size of this Layer to the given width and height. Resizing
     * is only attempted if the new size provided is actually different from
     * the current size.
     *
     * @param {Number} newWidth The new width to assign to this Layer.
     * @param {Number} newHeight The new height to assign to this Layer.
     */
    this.resize = function(newWidth, newHeight) {
        scheduleTask(function() {
            if (newWidth != width || newHeight != height)
                resize(newWidth, newHeight);
        });
    };

    /**
     * Draws the specified image at the given coordinates. The image specified
     * must already be loaded.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {Image} image The image to draw. Note that this is an Image
     *                      object - not a URL.
     */
    this.drawImage = function(x, y, image) {
        scheduleTask(function() {
            if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
            displayContext.drawImage(image, x, y);
        });
    };

    /**
     * Draws the image at the specified URL at the given coordinates. The image
     * will be loaded automatically, and this and any future operations will
     * wait for the image to finish loading.
     *
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     * @param {String} url The URL of the image to draw.
     */
    this.draw = function(x, y, url) {
        var task = scheduleTask(null);

        var image = new Image();
        image.onload = function() {

            task.handler = function() {
                if (layer.autosize != 0) fitRect(x, y, image.width, image.height);
                displayContext.drawImage(image, x, y);
            };

            // As this task originally had no handler and may have blocked
            // other tasks, handle any blocked tasks.
            handlePendingTasks();

        };
        image.src = url;

    };

    /**
     * Run an arbitrary function as soon as currently pending operations
     * are complete.
     *
     * @param {function} handler The function to call once all currently
     *                           pending operations are complete.
     */
    this.sync = function(handler) {
        scheduleTask(handler);
    };

    /**
     * Copy a rectangle of image data from one Layer to this Layer. This
     * operation will copy exactly the image data that will be drawn once all
     * operations of the source Layer that were pending at the time this
     * function was called are complete. This operation will not alter the
     * size of the source Layer even if its autosize property is set to true.
     *
     * @param {Guacamole.Layer} srcLayer The Layer to copy image data from.
     * @param {Number} srcx The X coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcy The Y coordinate of the upper-left corner of the
     *                      rectangle within the source Layer's coordinate
     *                      space to copy data from.
     * @param {Number} srcw The width of the rectangle within the source Layer's
     *                      coordinate space to copy data from.
     * @param {Number} srch The height of the rectangle within the source
     *                      Layer's coordinate space to copy data from.
     * @param {Number} x The destination X coordinate.
     * @param {Number} y The destination Y coordinate.
     */
    this.copyRect = function(srcLayer, srcx, srcy, srcw, srch, x, y) {

        function doCopyRect() {
            if (layer.autosize != 0) fitRect(x, y, srcw, srch);
            displayContext.drawImage(srcLayer.getCanvas(), srcx, srcy, srcw, srch, x, y, srcw, srch);
        }

        // If we ARE the source layer, no need to sync.
        // Syncing would result in deadlock.
        if (layer === srcLayer)
            scheduleTask(doCopyRect);

        // Otherwise synchronize copy operation with source layer
        else {
            var task = scheduleTask(null);
            srcLayer.sync(function() {

                task.handler = doCopyRect;

                // As this task originally had no handler and may have blocked
                // other tasks, handle any blocked tasks.
                handlePendingTasks();

            });
        }

    };

    /**
     * Clear the specified rectangle of image data.
     *
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to clear.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to clear.
     * @param {Number} w The width of the rectangle to clear.
     * @param {Number} h The height of the rectangle to clear.
     */
    this.clearRect = function(x, y, w, h) {
        scheduleTask(function() {
            if (layer.autosize != 0) fitRect(x, y, w, h);
            displayContext.clearRect(x, y, w, h);
        });
    };

    /**
     * Fill the specified rectangle of image data with the specified color.
     *
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to draw.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to draw.
     * @param {Number} w The width of the rectangle to draw.
     * @param {Number} h The height of the rectangle to draw.
     * @param {Number} r The red component of the color of the rectangle.
     * @param {Number} g The green component of the color of the rectangle.
     * @param {Number} b The blue component of the color of the rectangle.
     * @param {Number} a The alpha component of the color of the rectangle.
     */
    this.drawRect = function(x, y, w, h, r, g, b, a) {
        scheduleTask(function() {
            if (layer.autosize != 0) fitRect(x, y, w, h);
            displayContext.fillStyle = "rgba("
                        + r + "," + g + "," + b + "," + a / 255 + ")";
            displayContext.fillRect(x, y, w, h);
        });
    };

    /**
     * Clip all future drawing operations by the specified rectangle.
     *
     * @param {Number} x The X coordinate of the upper-left corner of the
     *                   rectangle to use for the clipping region.
     * @param {Number} y The Y coordinate of the upper-left corner of the
     *                   rectangle to use for the clipping region.
     * @param {Number} w The width of the rectangle to use for the clipping region.
     * @param {Number} h The height of the rectangle to use for the clipping region.
     */
    this.clipRect = function(x, y, w, h) {
        scheduleTask(function() {

            // Clear any current clipping region
            displayContext.restore();
            displayContext.save();

            if (layer.autosize != 0) fitRect(x, y, w, h);

            // Set new clipping region
            displayContext.beginPath();
            displayContext.rect(x, y, w, h);
            displayContext.clip();

        });
    };

    /**
     * Provides the given filtering function with a writable snapshot of
     * image data and the current width and height of the Layer.
     *
     * @param {function} filter A function which accepts an array of image
     *                          data (as returned by the canvas element's
     *                          display context's getImageData() function),
     *                          the width of the Layer, and the height of the
     *                          Layer as parameters, in that order. This
     *                          function must accomplish its filtering by
     *                          modifying the given image data array directly.
     */
    this.filter = function(filter) {
        scheduleTask(function() {
            var imageData = displayContext.getImageData(0, 0, width, height);
            filter(imageData.data, width, height);
            displayContext.putImageData(imageData, 0, 0);
        });
    };

    /**
     * Sets the channel mask for future operations on this Layer. The channel
     * mask is a Guacamole-specific compositing operation identifier with a
     * single bit representing each of four channels (in order): source image
     * where destination transparent, source where destination opaque,
     * destination where source transparent, and destination where source
     * opaque.
     *
     * @param {Number} mask The channel mask for future operations on this
     *                      Layer.
     */
    this.setChannelMask = function(mask) {
        scheduleTask(function() {
            displayContext.globalCompositeOperation = compositeOperation[mask];
        });
    };

    // Initialize canvas dimensions
    resize(width, height);

};
<|endoftext|>


<commit_before>import { expect } from 'chai';
import hashmap from '../src/hashmap';

describe('hashmap', () => {
    it('should be an object', () => {
        const map = hashmap();
        expect(map).to.be.an('object');
    });

    it('should support assigning and reading properties', () => {
        const map = hashmap();
        map.foo = 1;
        map.bar = 2;

        expect(map['foo']).to.equal(1); // eslint-disable-line dot-notation
        expect(map.bar).to.equal(2);
    });

    it('should support enumeration', () => {
        const map = hashmap();
        map.foo = 1;
        map.bar = 2;

        let i = 0;
        const keys = Object.keys(map);
        for (const prop in map) {
            expect(prop).to.equal(keys[i]);
            expect(map[prop]).to.equal(map[keys[i]]);
            i++;
        }
    });

    it('should not have a prototype or inherited properties/methods', () => {
        const map = hashmap();
        expect(Object.getPrototypeOf(map)).to.equal(null);
        let hasProps = false;
        for (const key in map) {
            hasProps = true;
        }
        expect(hasProps).to.equal(false);
    });

    it('should allow object literal as an argument to populate the map with its properties', () => {
        const map = hashmap({foo: 1, bar: 2});
        expect(map).to.eql({foo: 1, bar: 2});
    });

    it('should allow multiple object literals as parameters (right overwrites left)', () => {
        const map = hashmap({foo: 1, bar: 2}, {baz: 3, qux: 4}, {foo: 10});
        expect(map).to.eql({foo: 10, bar: 2, baz: 3, qux: 4});
    });

    it('should be iterable', () => {
        const map = hashmap({foo: 1, bar: 2});
        expect(Symbol.iterator in map).to.equal(true);
        const descriptor = Object.getOwnPropertyDescriptor(map, Symbol.iterator);
        expect(descriptor.configurable).to.equal(false);
        expect(descriptor.enumerable).to.equal(false);
        expect(descriptor.writable).to.equal(false);
    });

    it('should support for...of loops', () => {
        const map = hashmap({foo: 1, bar: 2});
        let i = 0;
        const keys = Object.keys(map);
        for (const [key, value] of map) {
            expect(key).to.equal(keys[i]);
            expect(value).to.equal(map[keys[i++]]);
        }
    });

    it('should support iterator syntax', () => {
        const map = hashmap({foo: 1, bar: 2});
        let i = 0;
        const entries = Object.entries(map);
        const iter = map[Symbol.iterator]();
        expect(iter.next().value).to.eql(entries[i++]);
        expect(iter.next().value).to.eql(entries[i++]);
        expect(iter.next().done).to.eql(true);
    });
});
<commit_msg>Fix linting error<commit_after>import { expect } from 'chai';
import hashmap from '../src/hashmap';

describe('hashmap', () => {
    it('should be an object', () => {
        const map = hashmap();
        expect(map).to.be.an('object');
    });

    it('should support assigning and reading properties', () => {
        const map = hashmap();
        map.foo = 1;
        map.bar = 2;

        expect(map['foo']).to.equal(1); // eslint-disable-line dot-notation
        expect(map.bar).to.equal(2);
    });

    it('should support enumeration', () => {
        const map = hashmap();
        map.foo = 1;
        map.bar = 2;

        let i = 0;
        const keys = Object.keys(map);
        for (const prop in map) {
            expect(prop).to.equal(keys[i]);
            expect(map[prop]).to.equal(map[keys[i]]);
            i++;
        }
    });

    it('should not have a prototype or inherited properties/methods', () => {
        const map = hashmap();
        expect(Object.getPrototypeOf(map)).to.equal(null);
        let hasProps = false;
        /* eslint-disable no-unused-vars */
        for (const key in map) {
            hasProps = true;
        }
        /* eslint-enable no-unused-vars */
        expect(hasProps).to.equal(false);
    });

    it('should allow object literal as an argument to populate the map with its properties', () => {
        const map = hashmap({foo: 1, bar: 2});
        expect(map).to.eql({foo: 1, bar: 2});
    });

    it('should allow multiple object literals as parameters (right overwrites left)', () => {
        const map = hashmap({foo: 1, bar: 2}, {baz: 3, qux: 4}, {foo: 10});
        expect(map).to.eql({foo: 10, bar: 2, baz: 3, qux: 4});
    });

    it('should be iterable', () => {
        const map = hashmap({foo: 1, bar: 2});
        expect(Symbol.iterator in map).to.equal(true);
        const descriptor = Object.getOwnPropertyDescriptor(map, Symbol.iterator);
        expect(descriptor.configurable).to.equal(false);
        expect(descriptor.enumerable).to.equal(false);
        expect(descriptor.writable).to.equal(false);
    });

    it('should support for...of loops', () => {
        const map = hashmap({foo: 1, bar: 2});
        let i = 0;
        const keys = Object.keys(map);
        for (const [key, value] of map) {
            expect(key).to.equal(keys[i]);
            expect(value).to.equal(map[keys[i++]]);
        }
    });

    it('should support iterator syntax', () => {
        const map = hashmap({foo: 1, bar: 2});
        let i = 0;
        const entries = Object.entries(map);
        const iter = map[Symbol.iterator]();
        expect(iter.next().value).to.eql(entries[i++]);
        expect(iter.next().value).to.eql(entries[i++]);
        expect(iter.next().done).to.eql(true);
    });
});
<|endoftext|>