<commit_before>var Code = require('code')

var Lab = require('@hapi/lab')
var lab = exports.lab = Lab.script()

var expect = Code.expect
var describe = lab.experiment
var it = lab.test
var beforeEach = lab.beforeEach
var afterEach = lab.afterEach

var fs = require('fs')
var http = require('http')
var https = require('https')
var net = require('net')
var util = require('util')

var SolrProxy = require('../index.js')

var createSolrTestDouble = function (responseCode) {
  var server = http.createServer(function (req, res) {
    res.writeHead(responseCode)
    res.end()
  })
  return server.listen(8080)
}

var checkResponseCode = util.promisify(function (client, url, expectedCode, done) {
  const parsed = new URL(url)
  const options = {
    port: parsed.port,
    path: parsed.pathname + parsed.search,
    rejectUnauthorized: false
  }

  client.get(options, (res) => {
    expect(res.statusCode).to.equal(expectedCode)
    done()
  })
})

describe('exports', function () {
  it('should expose a start function', function () {
    expect(typeof SolrProxy.start).to.equal('function')
  })
})

describe('start()', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    solrTestDouble = createSolrTestDouble(200)
  })

  afterEach(function () {
    proxy.close()
    solrTestDouble.close()
  })

  it('should start a proxy on specified port if port is specified', async function () {
    proxy = SolrProxy.start(9999)
    await checkResponseCode(http, 'http://localhost:9999/solr/select?q=fhqwhagads', 200)
  })

  it('should not start a proxy on the default port if a different port is specified', util.promisify(function (_, done) {
    proxy = SolrProxy.start(9999)

    http
      .get('http://localhost:8008/solr/select?q=fhqwhagads')
      .on('error', function (err) {
        expect(err.code).to.equal('ECONNREFUSED')
        done()
      })
  }))

  it('should use options if specified', async function () {
    proxy = SolrProxy.start(null, { validPaths: '/come/on' })
    await checkResponseCode(http, 'http://localhost:8008/come/on?q=fhqwhagads', 200)
  })

  it('should be able to start with TLS', async function () {
    var options = {
      ssl: {
        key: fs.readFileSync(`${__dirname}/fixtures/test_key.pem`),
        cert: fs.readFileSync(`${__dirname}/fixtures/test_cert.pem`)
      }
    }
    proxy = SolrProxy.start(null, options)
    await checkResponseCode(https, 'https://localhost:8008/solr/select?q=fhqwhagads', 200)
  })
})

describe('proxy server defaults', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    proxy = SolrProxy.start()
  })

  afterEach(function () {
    proxy.close()
    if (solrTestDouble.close) {
      solrTestDouble.close()
    }
  })

  it('should return 502 on proxy error', async function () {
    var server = net.createServer(function (c) {
      c.write('abc\r\n')
      c.end()
    })
    solrTestDouble = server.listen(8080)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 502)
  })

  it('should return 200 for a valid request', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 200)
  })

  it('should return 403 on POST requests', util.promisify(function (_, done) {
    solrTestDouble = createSolrTestDouble(200)

    http
      .request({
        hostname: 'localhost',
        port: 8008,
        path: '/solr/select',
        headers: {
          'Content-Type': 'application/json'
        },
        method: 'POST'
      })
      .on('response', function (response) {
        expect(response.statusCode).to.equal(403)
        done()
      })
      .end('{"q": "fhqwhgads"}')
  }))

  it('should return 403 on requests for /solr/admin', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/admin', 403)
  })

  it('should return 403 on request with qt parameter', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&qt=%2Fupdate', 403)
  })

  it('should return 403 on request with stream.url parameter', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&stream.url=EVERYBODYTOTHELIMIT!', 403)
  })

  it('should return 403 if rows param exceeds 200 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=201', 403)
  })

  it('should return 403 if start param exceeds 1000 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=1001', 403)
  })

  it('should return 200 if rows param does not exceed 200 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=199', 200)
  })

  it('should return 200 if start param does not exceed 1000 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=999', 200)
  })

  it('should return permit the query if rows param is not an integer', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=foobar', 200)
  })

  it('should return permit the query if start param is not an integer', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=foobar', 200)
  })
})

describe('proxy server rows', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    proxy = SolrProxy.start(null, { maxRows: 100 })
  })

  afterEach(function () {
    proxy.close()
    if (solrTestDouble.close) {
      solrTestDouble.close()
    }
  })

  it('should return 403 if rows param exceeds maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=101', 403)
  })

  it('should return 200 if rows param does not exceed maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=100', 200)
  })

  it('should return 200 if rows param is unspecified', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 200)
  })
})

describe('proxy server start', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    proxy = SolrProxy.start(null, { maxStart: 2000 })
  })

  afterEach(function () {
    proxy.close()
    if (solrTestDouble.close) {
      solrTestDouble.close()
    }
  })

  it('should return 403 if start param exceeds maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=2001', 403)
  })

  it('should return 200 if start param does not exceed maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=1001', 200)
  })

  it('should return 200 if start param is unspecified', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 200)
  })
})
<commit_msg>chore: modify test for linter update<commit_after>var Code = require('code')

var Lab = require('@hapi/lab')
var lab = exports.lab = Lab.script()

var expect = Code.expect
var describe = lab.experiment
var it = lab.test
var beforeEach = lab.beforeEach
var afterEach = lab.afterEach

var fs = require('fs')
var http = require('http')
var https = require('https')
var net = require('net')
var path = require('path')
var util = require('util')

var SolrProxy = require('../index.js')

var createSolrTestDouble = function (responseCode) {
  var server = http.createServer(function (req, res) {
    res.writeHead(responseCode)
    res.end()
  })
  return server.listen(8080)
}

var checkResponseCode = util.promisify(function (client, url, expectedCode, done) {
  const parsed = new URL(url)
  const options = {
    port: parsed.port,
    path: parsed.pathname + parsed.search,
    rejectUnauthorized: false
  }

  client.get(options, (res) => {
    expect(res.statusCode).to.equal(expectedCode)
    done()
  })
})

describe('exports', function () {
  it('should expose a start function', function () {
    expect(typeof SolrProxy.start).to.equal('function')
  })
})

describe('start()', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    solrTestDouble = createSolrTestDouble(200)
  })

  afterEach(function () {
    proxy.close()
    solrTestDouble.close()
  })

  it('should start a proxy on specified port if port is specified', async function () {
    proxy = SolrProxy.start(9999)
    await checkResponseCode(http, 'http://localhost:9999/solr/select?q=fhqwhagads', 200)
  })

  it('should not start a proxy on the default port if a different port is specified', util.promisify(function (_, done) {
    proxy = SolrProxy.start(9999)

    http
      .get('http://localhost:8008/solr/select?q=fhqwhagads')
      .on('error', function (err) {
        expect(err.code).to.equal('ECONNREFUSED')
        done()
      })
  }))

  it('should use options if specified', async function () {
    proxy = SolrProxy.start(null, { validPaths: '/come/on' })
    await checkResponseCode(http, 'http://localhost:8008/come/on?q=fhqwhagads', 200)
  })

  it('should be able to start with TLS', async function () {
    var options = {
      ssl: {
        key: fs.readFileSync(path.join(__dirname, '/fixtures/test_key.pem')),
        cert: fs.readFileSync(path.join(__dirname, '/fixtures/test_cert.pem'))
      }
    }
    proxy = SolrProxy.start(null, options)
    await checkResponseCode(https, 'https://localhost:8008/solr/select?q=fhqwhagads', 200)
  })
})

describe('proxy server defaults', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    proxy = SolrProxy.start()
  })

  afterEach(function () {
    proxy.close()
    if (solrTestDouble.close) {
      solrTestDouble.close()
    }
  })

  it('should return 502 on proxy error', async function () {
    var server = net.createServer(function (c) {
      c.write('abc\r\n')
      c.end()
    })
    solrTestDouble = server.listen(8080)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 502)
  })

  it('should return 200 for a valid request', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 200)
  })

  it('should return 403 on POST requests', util.promisify(function (_, done) {
    solrTestDouble = createSolrTestDouble(200)

    http
      .request({
        hostname: 'localhost',
        port: 8008,
        path: '/solr/select',
        headers: {
          'Content-Type': 'application/json'
        },
        method: 'POST'
      })
      .on('response', function (response) {
        expect(response.statusCode).to.equal(403)
        done()
      })
      .end('{"q": "fhqwhgads"}')
  }))

  it('should return 403 on requests for /solr/admin', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/admin', 403)
  })

  it('should return 403 on request with qt parameter', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&qt=%2Fupdate', 403)
  })

  it('should return 403 on request with stream.url parameter', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&stream.url=EVERYBODYTOTHELIMIT!', 403)
  })

  it('should return 403 if rows param exceeds 200 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=201', 403)
  })

  it('should return 403 if start param exceeds 1000 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=1001', 403)
  })

  it('should return 200 if rows param does not exceed 200 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=199', 200)
  })

  it('should return 200 if start param does not exceed 1000 default', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=999', 200)
  })

  it('should return permit the query if rows param is not an integer', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=foobar', 200)
  })

  it('should return permit the query if start param is not an integer', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=foobar', 200)
  })
})

describe('proxy server rows', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    proxy = SolrProxy.start(null, { maxRows: 100 })
  })

  afterEach(function () {
    proxy.close()
    if (solrTestDouble.close) {
      solrTestDouble.close()
    }
  })

  it('should return 403 if rows param exceeds maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=101', 403)
  })

  it('should return 200 if rows param does not exceed maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&rows=100', 200)
  })

  it('should return 200 if rows param is unspecified', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 200)
  })
})

describe('proxy server start', function () {
  var proxy
  var solrTestDouble

  beforeEach(function () {
    proxy = SolrProxy.start(null, { maxStart: 2000 })
  })

  afterEach(function () {
    proxy.close()
    if (solrTestDouble.close) {
      solrTestDouble.close()
    }
  })

  it('should return 403 if start param exceeds maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=2001', 403)
  })

  it('should return 200 if start param does not exceed maximum', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads&start=1001', 200)
  })

  it('should return 200 if start param is unspecified', async function () {
    solrTestDouble = createSolrTestDouble(200)
    await checkResponseCode(http, 'http://localhost:8008/solr/select?q=fhqwhagads', 200)
  })
})
<|endoftext|>


<commit_before>"use strict";

var expect = require('chai').expect;
var chain = require('..');

describe('chain-middleware', function() {
  it('should error if given invalid middleware', function() {
    expect(function() {
      chain('test');
    }).to.throw(/must be a function/);
    expect(function() {
      chain(42);
    }).to.throw(/must be a function/);
    expect(function() {
      chain('test', 42);
    }).to.throw(/must be a function/);
    expect(function() {
      chain(['test', 42]);
    }).to.throw(/must be a function/);
  });

  it('should error if given partially invalid middleware', function() {
    var mw = function(req, res, next) {};
    expect(function() {
      chain([mw, 42]);
    }).to.throw(/must be a function/);
    expect(function() {
      chain(mw, 'test');
    }).to.throw(/must be a function/);
  });

  it('should properly a terminating middleware chain', function() {
    chain(function(req, res, next) {
      req.asdf = true;
      res.jkl = false;
      next();
    }, function(req, res, next) {
      expect(req.asdf).to.be.true;
      expect(res.jkl).to.be.false;
    }, function(req, res, next) {
      throw new Error('Should not be called');
    })({}, {}, function() {
      throw new Error('Should not be called');
    });
  });
});
<commit_msg>Add test for completed middleware chain<commit_after>"use strict";

var expect = require('chai').expect;
var chain = require('..');

describe('chain-middleware', function() {
  it('should error if given invalid middleware', function() {
    expect(function() {
      chain('test');
    }).to.throw(/must be a function/);
    expect(function() {
      chain(42);
    }).to.throw(/must be a function/);
    expect(function() {
      chain('test', 42);
    }).to.throw(/must be a function/);
    expect(function() {
      chain(['test', 42]);
    }).to.throw(/must be a function/);
  });

  it('should error if given partially invalid middleware', function() {
    var mw = function(req, res, next) {};
    expect(function() {
      chain([mw, 42]);
    }).to.throw(/must be a function/);
    expect(function() {
      chain(mw, 'test');
    }).to.throw(/must be a function/);
  });

  it('should properly run a terminating middleware chain', function() {
    chain(function(req, res, next) {
      req.asdf = true;
      res.jkl = false;
      next();
    }, function(req, res, next) {
      expect(req.asdf).to.be.true;
      expect(res.jkl).to.be.false;
    }, function(req, res, next) {
      throw new Error('Should not be called');
    })({}, {}, function() {
      throw new Error('Should not be called');
    });
  });

  it('should properly run a completing middleware chain', function(done) {
    chain(function(req, res, next) {
      req.asdf = true;
      res.jkl = false;
      next();
    }, function(req, res, next) {
      expect(req.asdf).to.be.true;
      expect(res.jkl).to.be.false;
      next();
    }, function(req, res, next) {
      next();
    })({}, {}, done);
  });
});
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import OpenMode from 'axiom/fs/open_mode';
import Path from 'axiom/fs/path';
import WritableFileStreamBuffer from 'axiom/fs/stream/writable_file_stream_buffer';

/** @typedef JsExecuteContext$$module$axiom$fs$js$execute_context */
var JsExecuteContext;

/**
 * @param {JsExecuteContext} cx
 * @return {void}
 */
export var main = function(cx) {
  cx.ready();

  var argv = cx.getArg('_');

  if (argv.length !== 1 || cx.getArg('help', false)) {
    cx.stdout.write([
      'Usage: tee [-a] <file>',
      'Example: cat in.txt | tee -a out.txt | sort > sorted_out.txt',
      '',
      'Options:',
      '',
      '  -h|--help',
      '      Print this help message and exit.',
      '  -a|--append',
      '      Append to the output file rather than overwrite it.',
      '',
      'Copies standard input to standard output, saving a copy to a file.'
    ].join('\r\n') + '\r\n');
    cx.closeOk();
    return;
  }

  var filePath = Path.abs(cx.getPwd(), argv[0]);
  var fileMode = /** @type {!OpenMode} */
      (OpenMode.fromString(cx.getArg('a') ? 'wc' : 'wct'));
  var fileBuffer = new WritableFileStreamBuffer(
      cx.fileSystemManager, filePath, fileMode);

  cx.stdin.onData.addListener(function(data) {
    fileBuffer.write(data);
    cx.stdout.write(data);
  });

  cx.stdin.onEnd.listenOnce(function() {
    fileBuffer.flush();
    cx.closeOk();
  });

  cx.stdin.resume();
};

export default main;

main.signature = {
  '_': '@',
  'help|h': '?',
  'append|a': '?'
};
<commit_msg>Adapt to changes in WritableFileStreamBuffer<commit_after>// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import DataType from 'axiom/fs/data_type';
import OpenMode from 'axiom/fs/open_mode';
import Path from 'axiom/fs/path';
import WritableFileStreamBuffer from 'axiom/fs/stream/writable_file_stream_buffer';

/** @typedef JsExecuteContext$$module$axiom$fs$js$execute_context */
var JsExecuteContext;

/**
 * @param {JsExecuteContext} cx
 * @return {void}
 */
export var main = function(cx) {
  cx.ready();

  var argv = cx.getArg('_');

  if (argv.length !== 1 || cx.getArg('help', false)) {
    cx.stdout.write([
      'Usage: tee [-a] <file>',
      'Example: cat in.txt | tee -a out.txt | sort > sorted_out.txt',
      '',
      'Options:',
      '',
      '  -h|--help',
      '      Print this help message and exit.',
      '  -a|--append',
      '      Append to the output file rather than overwrite it.',
      '',
      'Copies standard input to standard output, saving a copy to a file.'
    ].join('\r\n') + '\r\n');
    cx.closeOk();
    return;
  }

  var filePath = Path.abs(cx.getPwd(), argv[0]);
  var fileMode = /** @type {!OpenMode} */
      (OpenMode.fromString(cx.getArg('a') ? 'wc' : 'wct'));
  var fileBuffer = new WritableFileStreamBuffer(
      cx.fileSystemManager, filePath, DataType.UTF8String, fileMode);

  cx.stdin.onData.addListener(function(data) {
    fileBuffer.write(data);
    cx.stdout.write(data);
  });

  cx.stdin.onEnd.listenOnce(function() {
    fileBuffer.flush();
    cx.closeOk();
  });

  cx.stdin.resume();
};

export default main;

main.signature = {
  '_': '@',
  'help|h': '?',
  'append|a': '?'
};
<|endoftext|>


<commit_before>var merge = require('../')
var test = require('tap').test

test('add keys in target that do not exist at the root', function (t) {
    var src = { key1: 'value1', key2: 'value2' }
    var target = {}

    var res = merge(target, src)

    t.deepEqual(target, {}, 'merge should be immutable')
    t.deepEqual(res, src)
    t.end()
})

test('merge existing simple keys in target at the roots', function (t) {
    var src = { key1: 'changed', key2: 'value2' }
    var target = { key1: 'value1', key3: 'value3' }

    var expected = {
        key1: 'changed',
        key2: 'value2',
        key3: 'value3'
    }

    t.deepEqual(target, { key1: 'value1', key3: 'value3' })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('merge nested objects into target', function (t) {
    var src = {
        key1: {
            subkey1: 'changed',
            subkey3: 'added'
        }
    }
    var target = {
        key1: {
            subkey1: 'value1',
            subkey2: 'value2'
        }
    }

    var expected = {
        key1: {
            subkey1: 'changed',
            subkey2: 'value2',
            subkey3: 'added'
        }
    }

    t.deepEqual(target, {
        key1: {
            subkey1: 'value1',
            subkey2: 'value2'
        }
    })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('replace simple key with nested object in target', function (t) {
    var src = {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        }
    }
    var target = {
        key1: 'value1',
        key2: 'value2'
    }

    var expected = {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        },
        key2: 'value2'
    }

    t.deepEqual(target, { key1: 'value1', key2: 'value2' })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should add nested object in target', function(t) {
    var src = {
        "b": {
            "c": {}
        }
    }

    var target = {
        "a": {}
    }

    var expected = {
        "a": {},
        "b": {
            "c": {}
        }
    }

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should clone source and target', function(t) {
    var src = {
        "b": {
            "c": "foo"
        }
    }

    var target = {
        "a": {
            "d": "bar"
        }
    }

    var expected = {
        "a": {
            "d": "bar"
        },
        "b": {
            "c": "foo"
        }
    }

    var merged = merge(target, src, {clone: true})

    t.deepEqual(merged, expected)

    t.notEqual(merged.a, target.a)
    t.notEqual(merged.b, src.b)

    t.end()
})

test('should not clone source and target', function(t) {
    var src = {
        "b": {
            "c": "foo"
        }
    }

    var target = {
        "a": {
            "d": "bar"
        }
    }

    var merged = merge(target, src)
    t.equal(merged.a, target.a)
    t.equal(merged.b, src.b)

    t.end()
})

test('should replace object with simple key in target', function (t) {
    var src = { key1: 'value1' }
    var target = {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        },
        key2: 'value2'
    }

    var expected = { key1: 'value1', key2: 'value2' }

    t.deepEqual(target, {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        },
        key2: 'value2'
    })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace objects with arrays', function(t) {
    var target = [
        { key1: { subkey: 'one' }}
    ]

    var src = [
        { key1: [ "subkey" ]}
    ]

    var expected = [
        { key1: [ "subkey" ]}
    ]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace dates with arrays', function(t) {
    var target = [
        { key1: new Date()}
    ]

    var src = [
        { key1: [ "subkey" ]}
    ]

    var expected = [
        { key1: [ "subkey" ]}
    ]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace null with arrays', function(t) {
    var target = {
        key1: null
    }

    var src = {
        key1: [ "subkey" ]
    }

    var expected = {
        key1: [ "subkey" ]
    }

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should work on simple array', function (t) {
    var src = ['one', 'three']
    var target = ['one', 'two']

    var expected = ['one', 'two', 'three']

    t.deepEqual(target, ['one', 'two'])
    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src)))
    t.end()
})

test('should work on another simple array', function(t) {
    var target = ["a1","a2","c1","f1","p1"];
    var src = ["t1","s1","c2","r1","p2","p3"];

    var expected = ["a1", "a2", "c1", "f1", "p1", "t1", "s1", "c2", "r1", "p2", "p3"]
    t.deepEqual(target, ["a1", "a2", "c1", "f1", "p1"])
    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src)))
    t.end()
})

test('should work on array properties', function (t) {
    var src = {
        key1: ['one', 'three'],
        key2: ['four']
    }
    var target = {
        key1: ['one', 'two']
    }

    var expected = {
        key1: ['one', 'two', 'three'],
        key2: ['four']
    }

    t.deepEqual(target, {
        key1: ['one', 'two']
    })

    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src).key1))
    t.ok(Array.isArray(merge(target, src).key2))
    t.end()
})

test('should work on array properties with clone option', function (t) {
    var src = {
        key1: ['one', 'three'],
        key2: ['four']
    }
    var target = {
        key1: ['one', 'two']
    }

    t.deepEqual(target, {
        key1: ['one', 'two']
    })
    var merged = merge(target, src, {clone: true})
    t.notEqual(merged.key1, src.key1)
    t.notEqual(merged.key1, target.key1)
    t.notEqual(merged.key2, src.key2)
    t.end()
})

test('should work on array of objects', function (t) {
    var src = [
        { key1: ['one', 'three'], key2: ['one'] },
        { key3: ['five'] }
    ]
    var target = [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ]

    var expected = [
        { key1: ['one', 'two', 'three'], key2: ['one'] },
        { key3: ['four', 'five'] }
    ]

    t.deepEqual(target, [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ])
    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src)), 'result should be an array')
    t.ok(Array.isArray(merge(target, src)[0].key1), 'subkey should be an array too')

    t.end()
})

test('should work on array of objects with clone option', function (t) {
    var src = [
        { key1: ['one', 'three'], key2: ['one'] },
        { key3: ['five'] }
    ]
    var target = [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ]

    var expected = [
        { key1: ['one', 'two', 'three'], key2: ['one'] },
        { key3: ['four', 'five'] }
    ]

    t.deepEqual(target, [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ])
    var merged = merge(target, src, {clone: true})
    t.deepEqual(merged, expected)
    t.ok(Array.isArray(merge(target, src)), 'result should be an array')
    t.ok(Array.isArray(merge(target, src)[0].key1), 'subkey should be an array too')
    t.notEqual(merged[0].key1, src[0].key1)
    t.notEqual(merged[0].key1, target[0].key1)
    t.notEqual(merged[0].key2, src[0].key2)
    t.notEqual(merged[1].key3, src[1].key3)
    t.notEqual(merged[1].key3, target[1].key3)
    t.end()
})

test('should work on arrays of nested objects', function(t) {
    var target = [
        { key1: { subkey: 'one' }}
    ]

    var src = [
        { key1: { subkey: 'two' }},
        { key2: { subkey: 'three' }}
    ]

    var expected = [
        { key1: { subkey: 'two' }},
        { key2: { subkey: 'three' }}
    ]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should treat regular expressions like primitive values', function (t) {
    var target = { key1: /abc/ }
    var src = { key1: /efg/ }
    var expected = { key1: /efg/ }

    t.deepEqual(merge(target, src), expected)
    t.deepEqual(merge(target, src).key1.test('efg'), true)
    t.end()
})

test('should treat regular expressions like primitive values and should not'
    + ' clone even with clone option',
    function (t) {
        var target = { key1: /abc/ }
        var src = { key1: /efg/ }

        var output = merge(target, src, {clone: true})

        t.equal(output.key1, src.key1)
        t.end()
    }
)

test('should treat dates like primitives', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')
    var tuesday = new Date('2016-09-28T01:18:12.761Z')

    var target = {
        key: monday
    }
    var source = {
        key: tuesday
    }

    var expected = {
        key: tuesday
    }
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.equal(actual.key.valueOf(), tuesday.valueOf())
    t.end()
})

test('should treat dates like primitives and should not clone even with clone'
    +  ' option', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')
    var tuesday = new Date('2016-09-28T01:18:12.761Z')

    var target = {
        key: monday
    }
    var source = {
        key: tuesday
    }

    var actual = merge(target, source, {clone: true})

    t.equal(actual.key, tuesday)
    t.end()
})

test('should work on array with null in it', function(t) {
    var target = []

    var src = [null]

    var expected = [null]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should clone array\'s element if it is object', function(t) {
    var a = { key: 'yup' }
    var target = []
    var source = [a]

    var output = merge(target, source, {clone: true})

    t.notEqual(output[0], a)
    t.equal(output[0].key, 'yup')
    t.end()
})

test('should clone an array property when there is no target array', function(t) {
    const someObject = {}
    var target = {}
    var source = { ary: [someObject]}
    var output = merge(target, source, { clone: true })

    t.deepEqual(output, { ary: [{}] })
    t.notEqual(output.ary[0], someObject)
    t.end()
})

test('should overwrite values when property is initialised but undefined', function(t) {
    var target1 = { value: [] }
    var target2 = { value: null }
    var target3 = { value: 2 }

    var src = { value: undefined }

    function hasUndefinedProperty(o) {
        t.ok(o.hasOwnProperty('value'))
        t.type(o.value, 'undefined')
    }

    hasUndefinedProperty(merge(target1, src))
    hasUndefinedProperty(merge(target2, src))
    hasUndefinedProperty(merge(target3, src))

    t.end()
})

test('null should be equal to null in an array', function(t) {
    var target = [null, 'dude']
    var source = [null, 'lol']

    var expected = [null, 'dude', 'lol']
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.end()
})

test('dates in an array should be compared correctly', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')

    var target = [monday, 'dude']
    var source = [monday, 'lol']

    var expected = [monday, 'dude', 'lol']
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.end()
})

test('dates should copy correctly in an array', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')
    var tuesday = new Date('2016-09-28T01:18:12.761Z')

    var target = [monday, 'dude']
    var source = [tuesday, 'lol']

    var expected = [monday, 'dude', tuesday, 'lol']
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.end()
})
<commit_msg>Failing test for #65<commit_after>var merge = require('../')
var test = require('tap').test

test('add keys in target that do not exist at the root', function (t) {
    var src = { key1: 'value1', key2: 'value2' }
    var target = {}

    var res = merge(target, src)

    t.deepEqual(target, {}, 'merge should be immutable')
    t.deepEqual(res, src)
    t.end()
})

test('merge existing simple keys in target at the roots', function (t) {
    var src = { key1: 'changed', key2: 'value2' }
    var target = { key1: 'value1', key3: 'value3' }

    var expected = {
        key1: 'changed',
        key2: 'value2',
        key3: 'value3'
    }

    t.deepEqual(target, { key1: 'value1', key3: 'value3' })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('merge nested objects into target', function (t) {
    var src = {
        key1: {
            subkey1: 'changed',
            subkey3: 'added'
        }
    }
    var target = {
        key1: {
            subkey1: 'value1',
            subkey2: 'value2'
        }
    }

    var expected = {
        key1: {
            subkey1: 'changed',
            subkey2: 'value2',
            subkey3: 'added'
        }
    }

    t.deepEqual(target, {
        key1: {
            subkey1: 'value1',
            subkey2: 'value2'
        }
    })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('replace simple key with nested object in target', function (t) {
    var src = {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        }
    }
    var target = {
        key1: 'value1',
        key2: 'value2'
    }

    var expected = {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        },
        key2: 'value2'
    }

    t.deepEqual(target, { key1: 'value1', key2: 'value2' })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should add nested object in target', function(t) {
    var src = {
        "b": {
            "c": {}
        }
    }

    var target = {
        "a": {}
    }

    var expected = {
        "a": {},
        "b": {
            "c": {}
        }
    }

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should clone source and target', function(t) {
    var src = {
        "b": {
            "c": "foo"
        }
    }

    var target = {
        "a": {
            "d": "bar"
        }
    }

    var expected = {
        "a": {
            "d": "bar"
        },
        "b": {
            "c": "foo"
        }
    }

    var merged = merge(target, src, {clone: true})

    t.deepEqual(merged, expected)

    t.notEqual(merged.a, target.a)
    t.notEqual(merged.b, src.b)

    t.end()
})

test('should not clone source and target', function(t) {
    var src = {
        "b": {
            "c": "foo"
        }
    }

    var target = {
        "a": {
            "d": "bar"
        }
    }

    var merged = merge(target, src)
    t.equal(merged.a, target.a)
    t.equal(merged.b, src.b)

    t.end()
})

test('should replace object with simple key in target', function (t) {
    var src = { key1: 'value1' }
    var target = {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        },
        key2: 'value2'
    }

    var expected = { key1: 'value1', key2: 'value2' }

    t.deepEqual(target, {
        key1: {
            subkey1: 'subvalue1',
            subkey2: 'subvalue2'
        },
        key2: 'value2'
    })
    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace objects with arrays', function(t) {
    var target = [
        { key1: { subkey: 'one' }}
    ]

    var src = [
        { key1: [ "subkey" ]}
    ]

    var expected = [
        { key1: [ "subkey" ]}
    ]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace arrays with objects', function(t) {
    var target = { key1: [ "subkey" ] }

    var src = { key1: { subkey: 'one' } }

    var expected = { key1: { subkey: 'one' } }

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace dates with arrays', function(t) {
    var target = [
        { key1: new Date()}
    ]

    var src = [
        { key1: [ "subkey" ]}
    ]

    var expected = [
        { key1: [ "subkey" ]}
    ]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should replace null with arrays', function(t) {
    var target = {
        key1: null
    }

    var src = {
        key1: [ "subkey" ]
    }

    var expected = {
        key1: [ "subkey" ]
    }

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should work on simple array', function (t) {
    var src = ['one', 'three']
    var target = ['one', 'two']

    var expected = ['one', 'two', 'three']

    t.deepEqual(target, ['one', 'two'])
    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src)))
    t.end()
})

test('should work on another simple array', function(t) {
    var target = ["a1","a2","c1","f1","p1"];
    var src = ["t1","s1","c2","r1","p2","p3"];

    var expected = ["a1", "a2", "c1", "f1", "p1", "t1", "s1", "c2", "r1", "p2", "p3"]
    t.deepEqual(target, ["a1", "a2", "c1", "f1", "p1"])
    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src)))
    t.end()
})

test('should work on array properties', function (t) {
    var src = {
        key1: ['one', 'three'],
        key2: ['four']
    }
    var target = {
        key1: ['one', 'two']
    }

    var expected = {
        key1: ['one', 'two', 'three'],
        key2: ['four']
    }

    t.deepEqual(target, {
        key1: ['one', 'two']
    })

    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src).key1))
    t.ok(Array.isArray(merge(target, src).key2))
    t.end()
})

test('should work on array properties with clone option', function (t) {
    var src = {
        key1: ['one', 'three'],
        key2: ['four']
    }
    var target = {
        key1: ['one', 'two']
    }

    t.deepEqual(target, {
        key1: ['one', 'two']
    })
    var merged = merge(target, src, {clone: true})
    t.notEqual(merged.key1, src.key1)
    t.notEqual(merged.key1, target.key1)
    t.notEqual(merged.key2, src.key2)
    t.end()
})

test('should work on array of objects', function (t) {
    var src = [
        { key1: ['one', 'three'], key2: ['one'] },
        { key3: ['five'] }
    ]
    var target = [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ]

    var expected = [
        { key1: ['one', 'two', 'three'], key2: ['one'] },
        { key3: ['four', 'five'] }
    ]

    t.deepEqual(target, [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ])
    t.deepEqual(merge(target, src), expected)
    t.ok(Array.isArray(merge(target, src)), 'result should be an array')
    t.ok(Array.isArray(merge(target, src)[0].key1), 'subkey should be an array too')

    t.end()
})

test('should work on array of objects with clone option', function (t) {
    var src = [
        { key1: ['one', 'three'], key2: ['one'] },
        { key3: ['five'] }
    ]
    var target = [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ]

    var expected = [
        { key1: ['one', 'two', 'three'], key2: ['one'] },
        { key3: ['four', 'five'] }
    ]

    t.deepEqual(target, [
        { key1: ['one', 'two'] },
        { key3: ['four'] }
    ])
    var merged = merge(target, src, {clone: true})
    t.deepEqual(merged, expected)
    t.ok(Array.isArray(merge(target, src)), 'result should be an array')
    t.ok(Array.isArray(merge(target, src)[0].key1), 'subkey should be an array too')
    t.notEqual(merged[0].key1, src[0].key1)
    t.notEqual(merged[0].key1, target[0].key1)
    t.notEqual(merged[0].key2, src[0].key2)
    t.notEqual(merged[1].key3, src[1].key3)
    t.notEqual(merged[1].key3, target[1].key3)
    t.end()
})

test('should work on arrays of nested objects', function(t) {
    var target = [
        { key1: { subkey: 'one' }}
    ]

    var src = [
        { key1: { subkey: 'two' }},
        { key2: { subkey: 'three' }}
    ]

    var expected = [
        { key1: { subkey: 'two' }},
        { key2: { subkey: 'three' }}
    ]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should treat regular expressions like primitive values', function (t) {
    var target = { key1: /abc/ }
    var src = { key1: /efg/ }
    var expected = { key1: /efg/ }

    t.deepEqual(merge(target, src), expected)
    t.deepEqual(merge(target, src).key1.test('efg'), true)
    t.end()
})

test('should treat regular expressions like primitive values and should not'
    + ' clone even with clone option',
    function (t) {
        var target = { key1: /abc/ }
        var src = { key1: /efg/ }

        var output = merge(target, src, {clone: true})

        t.equal(output.key1, src.key1)
        t.end()
    }
)

test('should treat dates like primitives', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')
    var tuesday = new Date('2016-09-28T01:18:12.761Z')

    var target = {
        key: monday
    }
    var source = {
        key: tuesday
    }

    var expected = {
        key: tuesday
    }
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.equal(actual.key.valueOf(), tuesday.valueOf())
    t.end()
})

test('should treat dates like primitives and should not clone even with clone'
    +  ' option', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')
    var tuesday = new Date('2016-09-28T01:18:12.761Z')

    var target = {
        key: monday
    }
    var source = {
        key: tuesday
    }

    var actual = merge(target, source, {clone: true})

    t.equal(actual.key, tuesday)
    t.end()
})

test('should work on array with null in it', function(t) {
    var target = []

    var src = [null]

    var expected = [null]

    t.deepEqual(merge(target, src), expected)
    t.end()
})

test('should clone array\'s element if it is object', function(t) {
    var a = { key: 'yup' }
    var target = []
    var source = [a]

    var output = merge(target, source, {clone: true})

    t.notEqual(output[0], a)
    t.equal(output[0].key, 'yup')
    t.end()
})

test('should clone an array property when there is no target array', function(t) {
    const someObject = {}
    var target = {}
    var source = { ary: [someObject]}
    var output = merge(target, source, { clone: true })

    t.deepEqual(output, { ary: [{}] })
    t.notEqual(output.ary[0], someObject)
    t.end()
})

test('should overwrite values when property is initialised but undefined', function(t) {
    var target1 = { value: [] }
    var target2 = { value: null }
    var target3 = { value: 2 }

    var src = { value: undefined }

    function hasUndefinedProperty(o) {
        t.ok(o.hasOwnProperty('value'))
        t.type(o.value, 'undefined')
    }

    hasUndefinedProperty(merge(target1, src))
    hasUndefinedProperty(merge(target2, src))
    hasUndefinedProperty(merge(target3, src))

    t.end()
})

test('null should be equal to null in an array', function(t) {
    var target = [null, 'dude']
    var source = [null, 'lol']

    var expected = [null, 'dude', 'lol']
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.end()
})

test('dates in an array should be compared correctly', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')

    var target = [monday, 'dude']
    var source = [monday, 'lol']

    var expected = [monday, 'dude', 'lol']
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.end()
})

test('dates should copy correctly in an array', function(t) {
    var monday = new Date('2016-09-27T01:08:12.761Z')
    var tuesday = new Date('2016-09-28T01:18:12.761Z')

    var target = [monday, 'dude']
    var source = [tuesday, 'lol']

    var expected = [monday, 'dude', tuesday, 'lol']
    var actual = merge(target, source)

    t.deepEqual(actual, expected)
    t.end()
})
<|endoftext|>


<commit_before>/*jslint node:true, nomen:true */

/*
 * xbee-promise
 * https://github.com/101100/xbee-promise
 *
 * Copyright (c) 2014 Jason Heard
 * Licensed under the MIT license.
 */

var parambulator = require('parambulator');
var q = require('q');
var SerialPort = require('serialport').SerialPort;
var xbee_api = require('xbee-api');
var util = require('util');

var optionsSpec = {
    serialport: 'required$, string$',
    api_mode: { enum$: [1, 2] },
    serialportOptions: 'object$',
    defaultTimeout: { gt$: 1000, type$: 'integer' }
};

module.exports = function xbeePromiseLibrary(options) {
    "use strict";

    var xbeeAPI,
        serialport,
        cachedNodes = {},
        debug = false,
        defaultTimeout;

    function closeSerialport() {
        if (debug) {
            console.log("Closing serial port");
        }

        serialport.drain(function closeAfterDraining() {
            serialport.close();
        });
    }

    function _createCallback(deferred, frameId, frameType) {
        return function deferredResolverCallback(receivedFrame) {
            if (receivedFrame.id === frameId && receivedFrame.type === frameType) {
                // This is our frame's response. Resolve the promise.
                deferred.resolve(receivedFrame);
            }
        };
    }

    // Returns a promise that will resolve to the response for the given
    // frame that will be sent.
    function _sendFramePromiseResponse(frame, timeoutMs, responseFrameType) {
        var frameId = xbeeAPI.nextFrameId(),
            deferred = q.defer(),
            callback;

        // Set the frame ID and create the callback to check for it
        frame.id = frameId;
        callback = _createCallback(deferred, frameId, responseFrameType);

        // Attach callback so we're waiting for the response
        xbeeAPI.on("frame_object", callback);

        // Write to the serialport (when open or now if open)
        if (serialport.paused) {
            serialport.on("open", function () {
                serialport.write(xbeeAPI.buildFrame(frame));
            });
        } else {
            serialport.write(xbeeAPI.buildFrame(frame));
        }

        // Return our promise with a timeout
        return deferred.promise.timeout(timeoutMs).fin(function () {
            // clean up: remove listener after the promise is complete (for any reason)
            xbeeAPI.removeListener("frame_object", callback);
        });
    }

    function _localCommand(command, timeoutMs, commandParameter) {
        var frame = {
                type: xbee_api.constants.FRAME_TYPE.AT_COMMAND,
                command: command,
                commandParameter: commandParameter
            };

        if (debug) {
            console.log("Doing local command ", command, "with parameter", commandParameter || []);
        }

        return _sendFramePromiseResponse(frame, timeoutMs, xbee_api.constants.FRAME_TYPE.AT_COMMAND_RESPONSE)
            .then(function (frame) {
                if (frame.commandStatus === xbee_api.constants.COMMAND_STATUS.OK) {
                    return frame.commandData;
                }

                // if not OK, throw error
                throw new Error(xbee_api.constants.COMMAND_STATUS[frame.commandStatus]);
            });
    }

    function _extractDestination64(commandData) {
        // Result in commandData is 16 bit address as two bytes,
        // followed by 64 bit address as 8 bytes.  This function
        // returns the 64 bit address.

        var address64 = commandData.slice(2, 10);

        if (debug) {
            console.log("Extracted 64 bit address:", address64);
        }

        return address64;
    }

    // Returns a promise that will resolve to the 64 bit address of the node
    // with the given node identifier.
    function _lookupByNodeIdentifier(nodeIdentifier, timeoutMs) {
        // if the address is cached, return that wrapped in a promise
        if (cachedNodes[nodeIdentifier]) {
            return q(cachedNodes[nodeIdentifier]);
        }

        if (debug) {
            console.log("Looking up", nodeIdentifier);
        }

        return _localCommand('DN', timeoutMs, nodeIdentifier)
            .then(_extractDestination64, function errorHandler() {
                // any error from _sendFramePromiseResponse implies node not found
                throw new Error("Node not found");
            })
            .then(function (address64) {
                // cache result
                cachedNodes[nodeIdentifier] = address64;
                return address64;
            });
    }

    function _remoteCommand(command, destination64, timeoutMs, commandParameter) {
        var frame = {
                type: xbee_api.constants.FRAME_TYPE.REMOTE_AT_COMMAND_REQUEST,
                command: command,
                commandParameter: commandParameter,
                destination64: destination64
            };

        if (debug) {
            console.log("Sending", command, "to", destination64, "with parameter", commandParameter || []);
        }

        return _sendFramePromiseResponse(frame, timeoutMs, xbee_api.constants.FRAME_TYPE.REMOTE_COMMAND_RESPONSE)
            .then(function (frame) {
                if (frame.commandStatus === xbee_api.constants.COMMAND_STATUS.OK) {
                    return frame.commandData;
                }

                // if not OK, throw error
                throw new Error(xbee_api.constants.COMMAND_STATUS[frame.commandStatus]);
            });
    }

    function _remoteTransmit(destination64, data, timeoutMs) {
        var frame = {
                type: xbee_api.constants.FRAME_TYPE.ZIGBEE_TRANSMIT_REQUEST,
                destination64: destination64,
                data: data
            };

        if (debug) {
            console.log("Sending '" + data + "' to", destination64);
        }

        return _sendFramePromiseResponse(frame, timeoutMs, xbee_api.constants.FRAME_TYPE.ZIGBEE_TRANSMIT_STATUS)
            .then(function (frame) {
                if (frame.deliveryStatus === xbee_api.constants.DELIVERY_STATUS.SUCCESS) {
                    return true;
                }

                // if not OK, throw error
                throw new Error(xbee_api.constants.DELIVERY_STATUS[frame.deliveryStatus]);
            });
    }

    function localCommand(settings) {
        var command,
            commandParameter,
            timeoutMs = (settings && settings.timeoutMs) || defaultTimeout;

        if (typeof settings !== "object" || typeof settings.command !== "string") {
            throw new Error("Expected a settings object with string property 'command'");
        }

        command = settings.command;

        if (util.isArray(settings.commandParameter) || typeof settings.commandParameter === "string") {
            commandParameter = settings.commandParameter;
        } else if (settings.commandParameter) {
            throw new Error("Settings property 'commandParameter' must be an array or a string");
        }

        return _localCommand(command, timeoutMs, commandParameter);
    }

    function remoteCommand(settings) {
        var command,
            commandParameter,
            timeoutMs = (settings && settings.timeoutMs) || defaultTimeout;

        if (typeof settings !== "object" || typeof settings.command !== "string") {
            throw new Error("Expected a settings object with string property 'command'");
        }

        command = settings.command;

        if (util.isArray(settings.commandParameter) || typeof settings.commandParameter === "string") {
            commandParameter = settings.commandParameter;
        } else if (settings.commandParameter) {
            throw new Error("Settings property 'commandParameter' must be an array or a string");
        }

        if (settings.destination64 && !util.isArray(settings.destination64) && typeof settings.destination64 !== "string") {
            throw new Error("Settings property 'destination64' must be an array or a string");
        }

        if (settings.destinationId && typeof settings.destinationId !== "string") {
            throw new Error("Settings property 'destinationId' must be a string");
        }

        if (settings.destination64) {
            return _remoteCommand(command, settings.destination64, timeoutMs, commandParameter);
        }

        if (settings.destinationId) {
            return _lookupByNodeIdentifier(settings.destinationId, timeoutMs)
                .then(function (lookupResult) {
                    cachedNodes[settings.destinationId] = lookupResult;
                    return _remoteCommand(command, lookupResult, timeoutMs, commandParameter);
                });
        }
    }

    // TODO test!
    function remoteTransmit(settings) {
        var data,
            timeoutMs = (settings && settings.timeoutMs) || defaultTimeout;

        if (typeof settings !== "object" || typeof settings.data !== "string") {
            throw new Error("Expected a settings object with string property 'data'");
        }

        data = settings.data;

        if (settings.destination64 && !util.isArray(settings.destination64) && typeof settings.destination64 !== "string") {
            throw new Error("Settings property 'destination64' must be an array or a string");
        }

        if (settings.destinationId && typeof settings.destinationId !== "string") {
            throw new Error("Settings property 'destinationId' must be a string");
        }

        if (settings.destination64) {
            return _remoteTransmit(settings.destination64, data, timeoutMs);
        }

        if (settings.destinationId) {
            return _lookupByNodeIdentifier(settings.destinationId, timeoutMs)
                .then(function (lookupResult) {
                    cachedNodes[settings.destinationId] = lookupResult;
                    return _remoteTransmit(lookupResult, data, timeoutMs);
                });
        }
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Initialization
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    parambulator(optionsSpec).validate(options, function (err) {
        if (err) {
            throw err;
        }
    });

    xbeeAPI = new xbee_api.XBeeAPI({
        api_mode: options.api_mode || 1
    });

    options.serialportOptions.parser = xbeeAPI.rawParser();

    // serialport = new SerialPort(options.serialport, {
    //     baudrate: 57600,
    //     parser: xbeeAPI.rawParser()
    // });

    serialport = new SerialPort(options.serialport, options.serialportOptions);

    if (options.debug) {
        debug = true;
    }

    defaultTimeout = options.defaultTimeout || 5000;

    if (debug) {
        xbeeAPI.on("frame_object", function (frame) {
            console.log("Debug frame:\n", frame);
        });
    }

    return {
        localCommand: localCommand,
        remoteCommand: remoteCommand,
        remoteTransmit: remoteTransmit,
        close: closeSerialport
    };
}; // end of xbeePromiseLibrary
<commit_msg>Remove comment<commit_after>/*jslint node:true, nomen:true */

/*
 * xbee-promise
 * https://github.com/101100/xbee-promise
 *
 * Copyright (c) 2014 Jason Heard
 * Licensed under the MIT license.
 */

var parambulator = require('parambulator');
var q = require('q');
var SerialPort = require('serialport').SerialPort;
var xbee_api = require('xbee-api');
var util = require('util');

var optionsSpec = {
    serialport: 'required$, string$',
    api_mode: { enum$: [1, 2] },
    serialportOptions: 'object$',
    defaultTimeout: { gt$: 1000, type$: 'integer' }
};

module.exports = function xbeePromiseLibrary(options) {
    "use strict";

    var xbeeAPI,
        serialport,
        cachedNodes = {},
        debug = false,
        defaultTimeout;

    function closeSerialport() {
        if (debug) {
            console.log("Closing serial port");
        }

        serialport.drain(function closeAfterDraining() {
            serialport.close();
        });
    }

    function _createCallback(deferred, frameId, frameType) {
        return function deferredResolverCallback(receivedFrame) {
            if (receivedFrame.id === frameId && receivedFrame.type === frameType) {
                // This is our frame's response. Resolve the promise.
                deferred.resolve(receivedFrame);
            }
        };
    }

    // Returns a promise that will resolve to the response for the given
    // frame that will be sent.
    function _sendFramePromiseResponse(frame, timeoutMs, responseFrameType) {
        var frameId = xbeeAPI.nextFrameId(),
            deferred = q.defer(),
            callback;

        // Set the frame ID and create the callback to check for it
        frame.id = frameId;
        callback = _createCallback(deferred, frameId, responseFrameType);

        // Attach callback so we're waiting for the response
        xbeeAPI.on("frame_object", callback);

        // Write to the serialport (when open or now if open)
        if (serialport.paused) {
            serialport.on("open", function () {
                serialport.write(xbeeAPI.buildFrame(frame));
            });
        } else {
            serialport.write(xbeeAPI.buildFrame(frame));
        }

        // Return our promise with a timeout
        return deferred.promise.timeout(timeoutMs).fin(function () {
            // clean up: remove listener after the promise is complete (for any reason)
            xbeeAPI.removeListener("frame_object", callback);
        });
    }

    function _localCommand(command, timeoutMs, commandParameter) {
        var frame = {
                type: xbee_api.constants.FRAME_TYPE.AT_COMMAND,
                command: command,
                commandParameter: commandParameter
            };

        if (debug) {
            console.log("Doing local command ", command, "with parameter", commandParameter || []);
        }

        return _sendFramePromiseResponse(frame, timeoutMs, xbee_api.constants.FRAME_TYPE.AT_COMMAND_RESPONSE)
            .then(function (frame) {
                if (frame.commandStatus === xbee_api.constants.COMMAND_STATUS.OK) {
                    return frame.commandData;
                }

                // if not OK, throw error
                throw new Error(xbee_api.constants.COMMAND_STATUS[frame.commandStatus]);
            });
    }

    function _extractDestination64(commandData) {
        // Result in commandData is 16 bit address as two bytes,
        // followed by 64 bit address as 8 bytes.  This function
        // returns the 64 bit address.

        var address64 = commandData.slice(2, 10);

        if (debug) {
            console.log("Extracted 64 bit address:", address64);
        }

        return address64;
    }

    // Returns a promise that will resolve to the 64 bit address of the node
    // with the given node identifier.
    function _lookupByNodeIdentifier(nodeIdentifier, timeoutMs) {
        // if the address is cached, return that wrapped in a promise
        if (cachedNodes[nodeIdentifier]) {
            return q(cachedNodes[nodeIdentifier]);
        }

        if (debug) {
            console.log("Looking up", nodeIdentifier);
        }

        return _localCommand('DN', timeoutMs, nodeIdentifier)
            .then(_extractDestination64, function errorHandler() {
                // any error from _sendFramePromiseResponse implies node not found
                throw new Error("Node not found");
            })
            .then(function (address64) {
                // cache result
                cachedNodes[nodeIdentifier] = address64;
                return address64;
            });
    }

    function _remoteCommand(command, destination64, timeoutMs, commandParameter) {
        var frame = {
                type: xbee_api.constants.FRAME_TYPE.REMOTE_AT_COMMAND_REQUEST,
                command: command,
                commandParameter: commandParameter,
                destination64: destination64
            };

        if (debug) {
            console.log("Sending", command, "to", destination64, "with parameter", commandParameter || []);
        }

        return _sendFramePromiseResponse(frame, timeoutMs, xbee_api.constants.FRAME_TYPE.REMOTE_COMMAND_RESPONSE)
            .then(function (frame) {
                if (frame.commandStatus === xbee_api.constants.COMMAND_STATUS.OK) {
                    return frame.commandData;
                }

                // if not OK, throw error
                throw new Error(xbee_api.constants.COMMAND_STATUS[frame.commandStatus]);
            });
    }

    function _remoteTransmit(destination64, data, timeoutMs) {
        var frame = {
                type: xbee_api.constants.FRAME_TYPE.ZIGBEE_TRANSMIT_REQUEST,
                destination64: destination64,
                data: data
            };

        if (debug) {
            console.log("Sending '" + data + "' to", destination64);
        }

        return _sendFramePromiseResponse(frame, timeoutMs, xbee_api.constants.FRAME_TYPE.ZIGBEE_TRANSMIT_STATUS)
            .then(function (frame) {
                if (frame.deliveryStatus === xbee_api.constants.DELIVERY_STATUS.SUCCESS) {
                    return true;
                }

                // if not OK, throw error
                throw new Error(xbee_api.constants.DELIVERY_STATUS[frame.deliveryStatus]);
            });
    }

    function localCommand(settings) {
        var command,
            commandParameter,
            timeoutMs = (settings && settings.timeoutMs) || defaultTimeout;

        if (typeof settings !== "object" || typeof settings.command !== "string") {
            throw new Error("Expected a settings object with string property 'command'");
        }

        command = settings.command;

        if (util.isArray(settings.commandParameter) || typeof settings.commandParameter === "string") {
            commandParameter = settings.commandParameter;
        } else if (settings.commandParameter) {
            throw new Error("Settings property 'commandParameter' must be an array or a string");
        }

        return _localCommand(command, timeoutMs, commandParameter);
    }

    function remoteCommand(settings) {
        var command,
            commandParameter,
            timeoutMs = (settings && settings.timeoutMs) || defaultTimeout;

        if (typeof settings !== "object" || typeof settings.command !== "string") {
            throw new Error("Expected a settings object with string property 'command'");
        }

        command = settings.command;

        if (util.isArray(settings.commandParameter) || typeof settings.commandParameter === "string") {
            commandParameter = settings.commandParameter;
        } else if (settings.commandParameter) {
            throw new Error("Settings property 'commandParameter' must be an array or a string");
        }

        if (settings.destination64 && !util.isArray(settings.destination64) && typeof settings.destination64 !== "string") {
            throw new Error("Settings property 'destination64' must be an array or a string");
        }

        if (settings.destinationId && typeof settings.destinationId !== "string") {
            throw new Error("Settings property 'destinationId' must be a string");
        }

        if (settings.destination64) {
            return _remoteCommand(command, settings.destination64, timeoutMs, commandParameter);
        }

        if (settings.destinationId) {
            return _lookupByNodeIdentifier(settings.destinationId, timeoutMs)
                .then(function (lookupResult) {
                    cachedNodes[settings.destinationId] = lookupResult;
                    return _remoteCommand(command, lookupResult, timeoutMs, commandParameter);
                });
        }
    }

    function remoteTransmit(settings) {
        var data,
            timeoutMs = (settings && settings.timeoutMs) || defaultTimeout;

        if (typeof settings !== "object" || typeof settings.data !== "string") {
            throw new Error("Expected a settings object with string property 'data'");
        }

        data = settings.data;

        if (settings.destination64 && !util.isArray(settings.destination64) && typeof settings.destination64 !== "string") {
            throw new Error("Settings property 'destination64' must be an array or a string");
        }

        if (settings.destinationId && typeof settings.destinationId !== "string") {
            throw new Error("Settings property 'destinationId' must be a string");
        }

        if (settings.destination64) {
            return _remoteTransmit(settings.destination64, data, timeoutMs);
        }

        if (settings.destinationId) {
            return _lookupByNodeIdentifier(settings.destinationId, timeoutMs)
                .then(function (lookupResult) {
                    cachedNodes[settings.destinationId] = lookupResult;
                    return _remoteTransmit(lookupResult, data, timeoutMs);
                });
        }
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Initialization
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    parambulator(optionsSpec).validate(options, function (err) {
        if (err) {
            throw err;
        }
    });

    xbeeAPI = new xbee_api.XBeeAPI({
        api_mode: options.api_mode || 1
    });

    options.serialportOptions.parser = xbeeAPI.rawParser();

    // serialport = new SerialPort(options.serialport, {
    //     baudrate: 57600,
    //     parser: xbeeAPI.rawParser()
    // });

    serialport = new SerialPort(options.serialport, options.serialportOptions);

    if (options.debug) {
        debug = true;
    }

    defaultTimeout = options.defaultTimeout || 5000;

    if (debug) {
        xbeeAPI.on("frame_object", function (frame) {
            console.log("Debug frame:\n", frame);
        });
    }

    return {
        localCommand: localCommand,
        remoteCommand: remoteCommand,
        remoteTransmit: remoteTransmit,
        close: closeSerialport
    };
}; // end of xbeePromiseLibrary
<|endoftext|>


<commit_before>'use strict';
/*global describe, it */
var chai  = require('chai')
  , chaiAsPromised = require('chai-as-promised')
  , ValidationError = require('../src/util/validation-error')
  , Promise = require('promise/src/es6-extensions')
  , mixed = require('../src/mixed')
  , object = require('../src/object')
  , string = require('../src/string')
  , reach = require('../src/util/reach');

chai.use(chaiAsPromised);
chai.should();

let noop = function(){}

describe( 'Mixed Types ', function(){

  it('should be immutable', function(){
    var inst = mixed(), next;
    var sub = inst.sub = mixed()

    inst.should.not.equal(next = inst.required())

    next.sub.should.equal(sub)
    inst.sub.should.equal(next.sub)

    inst.should.be.an.instanceOf(mixed)
    next.should.be.an.instanceOf(mixed)

    return Promise.all([
      inst.isValid().should.eventually.equal(true),
      next.isValid().should.eventually.equal(false)
    ])
  })

  it('cast should return a default is empty', function(){
    var inst = mixed().default('hello')

    return inst.cast().should.equal('hello')
  })

  it('should check types', function(){
    var inst = string().strict().typeError('must be a ${type}!')

    return Promise.all([
      inst.validate(5).should.be.rejected.then(function(err) {
        err.type.should.equal('typeError')
        err.message.should.equal('must be a string!')
        err.inner.length.should.equal(0)
      }),
      inst.validate(5, { abortEarly: false }).should.be.rejected.then(function(err) {
        chai.expect(err.type).to.not.exist
        err.message.should.equal('must be a string!')
        err.inner.length.should.equal(1)
      })
    ])
  })

  it('should limit values', function(){
    var inst = mixed().oneOf(['hello', 5])

    return Promise.all([
      inst.isValid(5).should.eventually.equal(true),
      inst.isValid('hello').should.eventually.equal(true),

      inst.validate(6).should.be.rejected.then(function(err) {
        err.errors[0].should.equal('this must be one the following values: hello, 5')
      })
    ])
  })

  it('should ignore absent values', function(){
    return  Promise.all([
      mixed()
        .oneOf(['hello'])
        .isValid(undefined)
        .should.eventually.equal(true),
      string()
        .nullable()
        .oneOf(['hello'])
        .isValid(null)
        .should.eventually.equal(true)
    ])
  })

  it('should exclude values', function(){
    var inst = mixed().notOneOf(['hello', 5])

    return Promise.all([
      inst.isValid(6).should.eventually.equal(true),
      inst.isValid('hfhfh').should.eventually.equal(true),

      inst.isValid(5).should.eventually.equal(false),

      inst.validate(5).should.be.rejected.then(function(err){
        err.errors[0].should.equal('this must not be one the following values: hello, 5')
      }),
      inst.oneOf([5]).isValid(5).should.eventually.equal(true)
    ])
  })

  it('should run subset of validations first', function(){
    var called = false;
    var inst = string()
      .strict()
      .test('test', 'boom', () => called = true)

    return inst.validate(25).should.be.rejected
      .then(() => {
        called.should.equal(false)
      })
  })

  it('should respect strict', function(){
    var inst = string().equals(['hello', '5'])

    return Promise.all([
      inst.isValid(5).should.eventually.equal(true),
      inst.strict().isValid(5).should.eventually.equal(false)
    ])
  })

  it('should respect abortEarly', function(){
    var inst = string().trim().min(10)

    return Promise.all([

      inst.strict().validate(' hi ').should.be.rejected
        .then(function(err){
          err.errors.length.should.equal(1)
        }),

      inst.strict().validate(' hi ', { abortEarly: false }).should.be.rejected
        .then(function(err){
          err.errors.length.should.equal(2)
        })
    ])
  })

  it('should dedupe tests with the same test function', function(){
    var inst = mixed()
      .test('test', ' ', noop)
      .test('test', 'asdasd', noop)

    inst.tests.length.should.equal(1)
    inst.tests[0].TEST.message.should.equal('asdasd')
  })

  it('should not dedupe tests with the same test function and different type', function(){
    var inst = mixed()
      .test('test', ' ', noop)
      .test('test-two', 'asdasd', noop)

    inst.tests.length.should.equal(2)
  })

  it('should respect exclusive validation', function(){
    var inst = mixed()
      .test({ message: 'invalid', exclusive: true, name: 'test', test:  function(){} })
      .test({ message: 'also invalid', name: 'test', test:  function(){} })

    inst.tests.length.should.equal(1)

    inst = mixed()
      .test({ message: 'invalid', name: 'test', test:  function(){} })
      .test({ message: 'also invalid', name: 'test', test:  function(){} })

    inst.tests.length.should.equal(2)
  })

  it('should non-exclusive tests should stack', function(){
    var inst = mixed()
      .test({ name: 'test', message: ' ', test:  function(){} })
      .test({ name: 'test', message: ' ', test:  function(){} })

    inst.tests.length.should.equal(2)
  })

  it('should replace existing tests, with exclusive test ', function(){
    var inst = mixed()
      .test({ name: 'test', message: ' ', test: function(){} })
      .test({ name: 'test', exclusive: true, message: ' ', test:  function(){} })

    inst.tests.length.should.equal(1)
  })

  it('should replace existing exclusive tests, with non-exclusive', function(){
    var inst = mixed()
      .test({ name: 'test', exclusive: true, message: ' ', test: function(){} })
      .test({ name: 'test', message: ' ', test:  function(){} })
      .test({ name: 'test', message: ' ', test:  function(){} })

    inst.tests.length.should.equal(2)
  })

  it('exclusive tests should throw without a name', function(){
    (function(){
      mixed().test({ message: 'invalid', exclusive: true, test: function(){} })
    }).should.throw()
  })

  it('exclusive tests should replace previous ones', function(){
    var inst = mixed().test({ message: 'invalid', exclusive: true, name: 'max', test: function(v){
      return v < 5
    }})

    return Promise.all([

      inst.isValid(8).should.eventually.become(false),

      inst.test({ message: 'invalid', exclusive: true, name: 'max', test: function(v){
          return v < 10
        }})
        .isValid(8).should.eventually.become(true)
    ])
  })

  it('tests should be called with the correct `this`', function(done){
    var inst = object({
      other: mixed(),
      test: mixed().test({
        message: 'invalid',
        exclusive: true,
        name: 'max',
        test() {
          this.path.should.equal('test')
          this.parent.should.eql({ other: 5, test : 'hi' })
          this.options.context.should.eql({ user: 'jason' })
          done()
        }
      })
    })

    inst.validate({ other: 5, test : 'hi' }, { context: { user: 'jason' } })
  })

  it('tests can return an error', function(){
    var inst = mixed().test({
        message: 'invalid ${path}',
        name: 'max',
        test() {
          return this.createError({ path: 'my.path' })
        }
      })

    return inst.validate('')
      .should.be.rejected
      .then(function(e){
        e.path.should.equal('my.path')
        e.errors[0].should.equal('invalid my.path')
      })
  })

  it('should use returned error path and message', function(){
    var inst = mixed().test({
        message: 'invalid ${path}',
        name: 'max',
        test: function(){
          return this.createError({ message: '${path} nope!', path: 'my.path' })
        }
      })

    return inst.validate({ other: 5, test : 'hi' })
      .should.be.rejected
      .then(function(e){
        e.path.should.equal('my.path')
        e.errors[0].should.equal('my.path nope!')
      })
  })

  it('should allow custom validation of either style', function(){
    var inst = string()
      .test('name', 'test a', function(val){
        return Promise.resolve(val === 'jim')
      })
      .test('name', 'test b', function(val, done){
        process.nextTick(function(){
          done(null, val !== 'jim')
        })
      }, true)

    return Promise.all([
      inst.validate('jim').should.be.rejected.then(function(e){
        e.errors[0].should.equal('test b')
      }),
      inst.validate('joe').should.be.rejected.then(function(e){
        e.errors[0].should.equal('test a')
      })
    ])
  })

  it('should respect callback interfaces', function(done){
    var inst = string().oneOf(['hello', '5'])

    inst.isValid(5, function(err, valid){
      valid.should.equal(true)
      chai.expect(err).to.equal(null)

      inst.strict().validate(5, function(err, value){
        err.should.be.an.instanceOf(ValidationError)
        chai.expect(value).to.equal(undefined)
        done()
      })
    })
  })

  it('should concat schemas', function(){
    var inst = object({
      str: string().required(),
      obj: object({
        str: string()
      })
    })

    var next = inst.concat(object({
      str: string().required().trim(),
      str2: string().required(),
      obj: object({
        str: string().required()
      })
    }))

    reach(next, 'str').tests.length.should.equal(3) // presence, alt presence, and trim
    reach(next, 'str').tests[0].TEST_NAME.should.equal('required') // make sure they are in the right order

    return Promise.all([

      inst.isValid({ str: 'hi', str2: 'hi', obj: {} }).should.become(true),

      next.validate({ str: ' hi  ', str2: 'hi', obj: { str: 'hi' } }).should.be.fulfilled.then(function(value){
        value.should.deep.eql({ str: 'hi', str2: 'hi', obj: {str: 'hi'} })
      }),

      next.validate({ str: 'hi', str2: 'hi', obj: {} }).should.be.rejected.then(function(err){
        err.message.should.contain('obj.str is a required field')
      }),

      next.validate({ str2: 'hi', obj: { str: 'hi'} }).should.be.rejected.then(function(err){
        err.message.should.contain('str is a required field')
      })
    ])

  })

  it('concat should fail on different types', function(){
    var inst = string().default('hi');

    (function(){
      inst.concat(object())
    }).should.throw(TypeError)
  })

  it('concat should allow mixed and other type', function(){
    var inst = mixed().default('hi');

    (function(){
      inst.concat(string())._type.should.equal('string')

    }).should.not.throw(TypeError)
  })

  it('concat should maintain undefined defaults', function(){
    var inst = string().default('hi')

    chai.expect(
      inst.concat(string().default(undefined)).default()).to.equal(undefined)
  })

  it('defaults should be validated but not transformed', function(){
    var inst = string().trim().default('  hi  ')

    return inst.validate(undefined).should.be.rejected
      .then(function(err){
        err.message.should.equal('this must be a trimmed string')
      })
  })

  it('should handle conditionals', function(){
    var inst = mixed()
      .when('prop', { is: 5, then: mixed().required('from parent') })

    return Promise.all([
      //parent
      inst._validate(undefined, {}, { parent: { prop: 5 }}).should.be.rejected,
      inst._validate(undefined, {}, { parent: { prop: 1 }}).should.be.fulfilled,
      inst._validate('hello', {},   { parent: { prop: 5 }}).should.be.fulfilled
    ])
    .then(function(){

      inst = string().when('prop', {
        is:        function(val) { return val === 5 },
        then:      string().required(),
        otherwise: string().min(4)
      })

      return Promise.all([
        inst._validate(undefined, {}, { parent: { prop: 5 }}).should.be.rejected,
        inst._validate('hello', {}, { parent: { prop: 1 }}).should.be.fulfilled,
        inst._validate('hel', {}, { parent: { prop: 1 }}).should.be.rejected
      ])
    })
  })

  it('should require context when needed', function(){
    var inst = mixed()
      .when('$prop', { is: 5, then: mixed().required('from context') })

    return Promise.all([
      inst._validate(undefined, { context: { prop: 5 }}, {}).should.be.rejected,
      inst._validate(undefined, { context: { prop: 1 }}, {}).should.be.fulfilled,
      inst._validate('hello',   { context: { prop: 5 }}, {}).should.be.fulfilled
    ])
    .then(function(){
      inst = string().when('$prop', {
        is:        function(val) { return val === 5 },
        then:      string().required(),
        otherwise: string().min(4)
      })

      return Promise.all([
        inst._validate(undefined, { context: { prop: 5 }}, {}).should.be.rejected,
        inst._validate('hello', { context: { prop: 1 }}, {}).should.be.fulfilled,
        inst._validate('hel', { context: { prop: 1 }}, {}).should.be.rejected
      ])
    })
  })

  it('should not use context refs in object calculations', function(){
    var inst = object({
      prop: string().when('$prop', { is: 5, then: string().required('from context') })
    })

    inst.default().should.eql({ prop: undefined })
  })

})
<commit_msg>changes mixed().oneOf() test to account for undefined order of iteration for the new Set class<commit_after>'use strict';
/*global describe, it */
var chai  = require('chai')
  , chaiAsPromised = require('chai-as-promised')
  , ValidationError = require('../src/util/validation-error')
  , Promise = require('promise/src/es6-extensions')
  , mixed = require('../src/mixed')
  , object = require('../src/object')
  , string = require('../src/string')
  , reach = require('../src/util/reach');

chai.use(chaiAsPromised);
chai.should();

let noop = function(){}

describe( 'Mixed Types ', function(){

  it('should be immutable', function(){
    var inst = mixed(), next;
    var sub = inst.sub = mixed()

    inst.should.not.equal(next = inst.required())

    next.sub.should.equal(sub)
    inst.sub.should.equal(next.sub)

    inst.should.be.an.instanceOf(mixed)
    next.should.be.an.instanceOf(mixed)

    return Promise.all([
      inst.isValid().should.eventually.equal(true),
      next.isValid().should.eventually.equal(false)
    ])
  })

  it('cast should return a default is empty', function(){
    var inst = mixed().default('hello')

    return inst.cast().should.equal('hello')
  })

  it('should check types', function(){
    var inst = string().strict().typeError('must be a ${type}!')

    return Promise.all([
      inst.validate(5).should.be.rejected.then(function(err) {
        err.type.should.equal('typeError')
        err.message.should.equal('must be a string!')
        err.inner.length.should.equal(0)
      }),
      inst.validate(5, { abortEarly: false }).should.be.rejected.then(function(err) {
        chai.expect(err.type).to.not.exist
        err.message.should.equal('must be a string!')
        err.inner.length.should.equal(1)
      })
    ])
  })

  it('should limit values', function(){
    var inst = mixed().oneOf([5, 'hello'])

    return Promise.all([
      inst.isValid(5).should.eventually.equal(true),
      inst.isValid('hello').should.eventually.equal(true),

      inst.validate(6).should.be.rejected.then(function(err) {
        err.errors[0].should.equal('this must be one the following values: 5, hello')
      })
    ])
  })

  it('should ignore absent values', function(){
    return  Promise.all([
      mixed()
        .oneOf(['hello'])
        .isValid(undefined)
        .should.eventually.equal(true),
      string()
        .nullable()
        .oneOf(['hello'])
        .isValid(null)
        .should.eventually.equal(true)
    ])
  })

  it('should exclude values', function(){
    var inst = mixed().notOneOf([5, 'hello'])

    return Promise.all([
      inst.isValid(6).should.eventually.equal(true),
      inst.isValid('hfhfh').should.eventually.equal(true),

      inst.isValid(5).should.eventually.equal(false),

      inst.validate(5).should.be.rejected.then(function(err){
        err.errors[0].should.equal('this must not be one the following values: 5, hello')
      }),
      inst.oneOf([5]).isValid(5).should.eventually.equal(true)
    ])
  })

  it('should run subset of validations first', function(){
    var called = false;
    var inst = string()
      .strict()
      .test('test', 'boom', () => called = true)

    return inst.validate(25).should.be.rejected
      .then(() => {
        called.should.equal(false)
      })
  })

  it('should respect strict', function(){
    var inst = string().equals(['hello', '5'])

    return Promise.all([
      inst.isValid(5).should.eventually.equal(true),
      inst.strict().isValid(5).should.eventually.equal(false)
    ])
  })

  it('should respect abortEarly', function(){
    var inst = string().trim().min(10)

    return Promise.all([

      inst.strict().validate(' hi ').should.be.rejected
        .then(function(err){
          err.errors.length.should.equal(1)
        }),

      inst.strict().validate(' hi ', { abortEarly: false }).should.be.rejected
        .then(function(err){
          err.errors.length.should.equal(2)
        })
    ])
  })

  it('should dedupe tests with the same test function', function(){
    var inst = mixed()
      .test('test', ' ', noop)
      .test('test', 'asdasd', noop)

    inst.tests.length.should.equal(1)
    inst.tests[0].TEST.message.should.equal('asdasd')
  })

  it('should not dedupe tests with the same test function and different type', function(){
    var inst = mixed()
      .test('test', ' ', noop)
      .test('test-two', 'asdasd', noop)

    inst.tests.length.should.equal(2)
  })

  it('should respect exclusive validation', function(){
    var inst = mixed()
      .test({ message: 'invalid', exclusive: true, name: 'test', test:  function(){} })
      .test({ message: 'also invalid', name: 'test', test:  function(){} })

    inst.tests.length.should.equal(1)

    inst = mixed()
      .test({ message: 'invalid', name: 'test', test:  function(){} })
      .test({ message: 'also invalid', name: 'test', test:  function(){} })

    inst.tests.length.should.equal(2)
  })

  it('should non-exclusive tests should stack', function(){
    var inst = mixed()
      .test({ name: 'test', message: ' ', test:  function(){} })
      .test({ name: 'test', message: ' ', test:  function(){} })

    inst.tests.length.should.equal(2)
  })

  it('should replace existing tests, with exclusive test ', function(){
    var inst = mixed()
      .test({ name: 'test', message: ' ', test: function(){} })
      .test({ name: 'test', exclusive: true, message: ' ', test:  function(){} })

    inst.tests.length.should.equal(1)
  })

  it('should replace existing exclusive tests, with non-exclusive', function(){
    var inst = mixed()
      .test({ name: 'test', exclusive: true, message: ' ', test: function(){} })
      .test({ name: 'test', message: ' ', test:  function(){} })
      .test({ name: 'test', message: ' ', test:  function(){} })

    inst.tests.length.should.equal(2)
  })

  it('exclusive tests should throw without a name', function(){
    (function(){
      mixed().test({ message: 'invalid', exclusive: true, test: function(){} })
    }).should.throw()
  })

  it('exclusive tests should replace previous ones', function(){
    var inst = mixed().test({ message: 'invalid', exclusive: true, name: 'max', test: function(v){
      return v < 5
    }})

    return Promise.all([

      inst.isValid(8).should.eventually.become(false),

      inst.test({ message: 'invalid', exclusive: true, name: 'max', test: function(v){
          return v < 10
        }})
        .isValid(8).should.eventually.become(true)
    ])
  })

  it('tests should be called with the correct `this`', function(done){
    var inst = object({
      other: mixed(),
      test: mixed().test({
        message: 'invalid',
        exclusive: true,
        name: 'max',
        test() {
          this.path.should.equal('test')
          this.parent.should.eql({ other: 5, test : 'hi' })
          this.options.context.should.eql({ user: 'jason' })
          done()
        }
      })
    })

    inst.validate({ other: 5, test : 'hi' }, { context: { user: 'jason' } })
  })

  it('tests can return an error', function(){
    var inst = mixed().test({
        message: 'invalid ${path}',
        name: 'max',
        test() {
          return this.createError({ path: 'my.path' })
        }
      })

    return inst.validate('')
      .should.be.rejected
      .then(function(e){
        e.path.should.equal('my.path')
        e.errors[0].should.equal('invalid my.path')
      })
  })

  it('should use returned error path and message', function(){
    var inst = mixed().test({
        message: 'invalid ${path}',
        name: 'max',
        test: function(){
          return this.createError({ message: '${path} nope!', path: 'my.path' })
        }
      })

    return inst.validate({ other: 5, test : 'hi' })
      .should.be.rejected
      .then(function(e){
        e.path.should.equal('my.path')
        e.errors[0].should.equal('my.path nope!')
      })
  })

  it('should allow custom validation of either style', function(){
    var inst = string()
      .test('name', 'test a', function(val){
        return Promise.resolve(val === 'jim')
      })
      .test('name', 'test b', function(val, done){
        process.nextTick(function(){
          done(null, val !== 'jim')
        })
      }, true)

    return Promise.all([
      inst.validate('jim').should.be.rejected.then(function(e){
        e.errors[0].should.equal('test b')
      }),
      inst.validate('joe').should.be.rejected.then(function(e){
        e.errors[0].should.equal('test a')
      })
    ])
  })

  it('should respect callback interfaces', function(done){
    var inst = string().oneOf(['hello', '5'])

    inst.isValid(5, function(err, valid){
      valid.should.equal(true)
      chai.expect(err).to.equal(null)

      inst.strict().validate(5, function(err, value){
        err.should.be.an.instanceOf(ValidationError)
        chai.expect(value).to.equal(undefined)
        done()
      })
    })
  })

  it('should concat schemas', function(){
    var inst = object({
      str: string().required(),
      obj: object({
        str: string()
      })
    })

    var next = inst.concat(object({
      str: string().required().trim(),
      str2: string().required(),
      obj: object({
        str: string().required()
      })
    }))

    reach(next, 'str').tests.length.should.equal(3) // presence, alt presence, and trim
    reach(next, 'str').tests[0].TEST_NAME.should.equal('required') // make sure they are in the right order

    return Promise.all([

      inst.isValid({ str: 'hi', str2: 'hi', obj: {} }).should.become(true),

      next.validate({ str: ' hi  ', str2: 'hi', obj: { str: 'hi' } }).should.be.fulfilled.then(function(value){
        value.should.deep.eql({ str: 'hi', str2: 'hi', obj: {str: 'hi'} })
      }),

      next.validate({ str: 'hi', str2: 'hi', obj: {} }).should.be.rejected.then(function(err){
        err.message.should.contain('obj.str is a required field')
      }),

      next.validate({ str2: 'hi', obj: { str: 'hi'} }).should.be.rejected.then(function(err){
        err.message.should.contain('str is a required field')
      })
    ])

  })

  it('concat should fail on different types', function(){
    var inst = string().default('hi');

    (function(){
      inst.concat(object())
    }).should.throw(TypeError)
  })

  it('concat should allow mixed and other type', function(){
    var inst = mixed().default('hi');

    (function(){
      inst.concat(string())._type.should.equal('string')

    }).should.not.throw(TypeError)
  })

  it('concat should maintain undefined defaults', function(){
    var inst = string().default('hi')

    chai.expect(
      inst.concat(string().default(undefined)).default()).to.equal(undefined)
  })

  it('defaults should be validated but not transformed', function(){
    var inst = string().trim().default('  hi  ')

    return inst.validate(undefined).should.be.rejected
      .then(function(err){
        err.message.should.equal('this must be a trimmed string')
      })
  })

  it('should handle conditionals', function(){
    var inst = mixed()
      .when('prop', { is: 5, then: mixed().required('from parent') })

    return Promise.all([
      //parent
      inst._validate(undefined, {}, { parent: { prop: 5 }}).should.be.rejected,
      inst._validate(undefined, {}, { parent: { prop: 1 }}).should.be.fulfilled,
      inst._validate('hello', {},   { parent: { prop: 5 }}).should.be.fulfilled
    ])
    .then(function(){

      inst = string().when('prop', {
        is:        function(val) { return val === 5 },
        then:      string().required(),
        otherwise: string().min(4)
      })

      return Promise.all([
        inst._validate(undefined, {}, { parent: { prop: 5 }}).should.be.rejected,
        inst._validate('hello', {}, { parent: { prop: 1 }}).should.be.fulfilled,
        inst._validate('hel', {}, { parent: { prop: 1 }}).should.be.rejected
      ])
    })
  })

  it('should require context when needed', function(){
    var inst = mixed()
      .when('$prop', { is: 5, then: mixed().required('from context') })

    return Promise.all([
      inst._validate(undefined, { context: { prop: 5 }}, {}).should.be.rejected,
      inst._validate(undefined, { context: { prop: 1 }}, {}).should.be.fulfilled,
      inst._validate('hello',   { context: { prop: 5 }}, {}).should.be.fulfilled
    ])
    .then(function(){
      inst = string().when('$prop', {
        is:        function(val) { return val === 5 },
        then:      string().required(),
        otherwise: string().min(4)
      })

      return Promise.all([
        inst._validate(undefined, { context: { prop: 5 }}, {}).should.be.rejected,
        inst._validate('hello', { context: { prop: 1 }}, {}).should.be.fulfilled,
        inst._validate('hel', { context: { prop: 1 }}, {}).should.be.rejected
      ])
    })
  })

  it('should not use context refs in object calculations', function(){
    var inst = object({
      prop: string().when('$prop', { is: 5, then: string().required('from context') })
    })

    inst.default().should.eql({ prop: undefined })
  })

})
<|endoftext|>


<commit_before>export class ViewSlotMock {
  constructor() {
    this.children = [];
  }
  removeAll(){}
  add(view){
    this.children.push(view);
  }
  insert(index, view){
    this.children.splice(index, 0, view);
  }
  removeAt(index){
    if(index < 0) {
      throw "negative index";
    }
    this.children.splice(index, 1);
  }
}

export  class ViewMock {
  bind(overrideContext){
    this.overrideContext = overrideContext;
  }
  attached(){}
  detached(){}
  unbind(){}
  returnToCache(){}
}

export class BoundViewFactoryMock {
  create(){};
  removeAll(){};
}

export class ViewFactoryMock {
  create(){
    return new ViewMock();
  }
}

export class ArrayObserverMock {
  subscribe(){};
  unsubscribe(){};
}

export class CollectionStrategyMock {
  initialize() {}
  processItems() {}
  getCollectionObserver() {}
  dispose() {}
}
<commit_msg>test(view-mock): fix constructor<commit_after>export class ViewSlotMock {
  constructor() {
    this.children = [];
  }
  removeAll(){}
  add(view){
    this.children.push(view);
  }
  insert(index, view){
    this.children.splice(index, 0, view);
  }
  removeAt(index){
    if(index < 0) {
      throw "negative index";
    }
    this.children.splice(index, 1);
  }
}

export class ViewMock {
  bind(bindingContext, overrideContext) {
    this.bindingContext = bindingContext;
    this.overrideContext = overrideContext;
  }
  attached(){}
  detached(){}
  unbind(){}
  returnToCache(){}
}

export class BoundViewFactoryMock {
  create(){};
  removeAll(){};
}

export class ViewFactoryMock {
  create(){
    return new ViewMock();
  }
}

export class ArrayObserverMock {
  subscribe(){};
  unsubscribe(){};
}

export class CollectionStrategyMock {
  initialize() {}
  processItems() {}
  getCollectionObserver() {}
  dispose() {}
}
<|endoftext|>