<commit_before>var {
  skanaar,
  processImports,
  processAsyncImports,
  ImportDepthError,
  renderSvg,
} = require('../dist/nomnoml.js')
var TestSuite = require('./TestSuite.js')

var suite = TestSuite('Misc')
var assert = TestSuite.assert

suite.node_test('assert library version', function () {
  var fs = require('fs')
  var library = require('../dist/nomnoml.js')
  var package = require('../package.json')
  var changelog = fs.readFileSync('changelog.md', { encoding: 'utf-8' })
  var versionMatch = changelog.match(/\d+\.\d+\.\d+/)
  var logVersion = versionMatch && versionMatch[0]
  if (library.version != package.version) {
    throw new Error('version of distribution bundle and npm package must match')
  }
  var isProductionVersion = !library.version.includes('-')
  if (isProductionVersion && library.version != logVersion) {
    throw new Error('production versions must be documented in changelog')
  }
})

suite.test('skanaar.testsuite.isEqual', function () {
  test(TestSuite.isEqual([1, 2], [1, 2]))
  test(!TestSuite.isEqual([2, 1], [1, 2]))
  test(TestSuite.isEqual({ b: 4, a: 'asdf' }, { a: 'asdf', b: 4 }))
  test(!TestSuite.isEqual({ b: 4, a: 'asdf' }, { a: 'asdf' }))
  test(!TestSuite.isEqual({ a: 'asdf' }, { a: 'asdf', b: 4 }))

  function test(condition) {
    if (!condition) throw new Error('testsuite failure')
  }
})

suite.test('skanaar.testsuite.assert', function () {
  TestSuite.assert([1, 2], '=', [1, 2])
  TestSuite.assert(() => TestSuite.assert([1, 2], '=', [17]), 'throws', Error)
  TestSuite.assert(4, '<', 5)
  TestSuite.assert(() => TestSuite.assert(5, '<', 4), 'throws', Error)
  TestSuite.assert(6, '>', 5)
  TestSuite.assert(() => TestSuite.assert(3, '>', 4), 'throws', Error)
  TestSuite.assert(() => TestSuite.assert(true), 'throws', Error)
})

suite.test('util range', function () {
  assert(skanaar.range([0, 1], 5), '=', [0, 0.25, 0.5, 0.75, 1])
})
suite.test('util sum', function () {
  assert(
    skanaar.sum([[0], [4], [17]], (e) => e[0]),
    '=',
    21
  )
  assert(
    skanaar.sum([[0], [4], [17]], (e) => e[0]),
    '=',
    21
  )
})
suite.test('util find', function () {
  assert(
    skanaar.find([{ a: 0 }, { a: 4, needle: true }, { a: 17 }], (e) => e.a == 4),
    '=',
    { a: 4, needle: true }
  )
})
suite.test('util last', function () {
  assert(skanaar.last([{ a: 0 }, { a: 4 }, { a: 17, needle: true }]), '=', { a: 17, needle: true })
})
suite.test('util hasSubstring', function () {
  assert(skanaar.hasSubstring('xyz abc', 'xyz'), '=', true)
  assert(skanaar.hasSubstring('1 xyz 0', 'xyz'), '=', true)
  assert(skanaar.hasSubstring('abc xyz', 'xyz'), '=', true)
})
suite.test('util indexBy', function () {
  assert(skanaar.indexBy([], 'name'), '=', {})
  assert(skanaar.indexBy([{ name: 'apa' }], 'name'), '=', { apa: { name: 'apa' } })
})
suite.test('util uniqueBy', function () {
  assert(
    skanaar.uniqueBy(
      [
        { a: 4, b: 'x' },
        { a: 17, c: 'y' },
        { a: 4, d: 'z' },
      ],
      'a'
    ),
    '=',
    [
      { a: 4, b: 'x' },
      { a: 17, c: 'y' },
    ]
  )
})

suite.test('processImports resolves shallow imports', function () {
  var mockFiles = {
    importsOneFile: '#import: file1',
    importsTwoFiles: '#import: file1\n#import: file2',
    file1: 'file1-contents',
    file2: 'file2-contents',
  }
  var loadFile = (key) => mockFiles[key]
  var output = processImports(mockFiles['importsOneFile'], loadFile)
  assert(output, '=', 'file1-contents')
  output = processImports(mockFiles['importsTwoFiles'], loadFile)
  assert(output, '=', 'file1-contents\nfile2-contents')
})

suite.test('processImports ignores bad imports', function () {
  var out = processImports('#import: root', (key) => {
    throw new Error('filesystem bug')
  })
  assert(out, '=', '')
})

suite.test('processImports throw on recursive imports', function () {
  TestSuite.assert(
    () => processImports('#import: root', (key) => '#import: root'),
    'throws',
    ImportDepthError
  )
})

var mockFiles = {
  root: `
A
#import: file1
B
#import: file2
C`,
  file1: `
X
#import: file3
Y`,
  file2: 'Q',
  file3: 'W',
}

var mockResult = `
A

X
W
Y
B
Q
C`

suite.test('processImports resolves deep imports', function () {
  var output = processImports(mockFiles['root'], (key) => mockFiles[key])
  assert(output, '=', mockResult)
})

suite.test('processAsyncImports resolves deep imports', async function () {
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
  var output = await processAsyncImports(mockFiles['root'], (key) =>
    delay(1).then(() => mockFiles[key])
  )
  assert(output, '=', mockResult)
})

suite.node_test('processAsyncImports via file system', async function () {
  var fs = require('fs')
  var output = await processAsyncImports(
    fs.readFileSync('test/import-test.nomnoml', 'utf-8'),
    (key) => fs.promises.readFile('test/' + key, 'utf-8')
  )
  fs.writeFileSync('test/output.async.nomnoml', output)
})

suite.test('escape [data-name] attribute value in SVG', function () {
  var output = renderSvg('[&]-[<]')
  assert(output, 'includes', 'data-name="&"')
})

suite.test('SVG export includes nomnoml source in <desc>', function () {
  var output = renderSvg('[a]->[b]')
  var desc = output.match('<desc ?>(.*)</desc>')[1]
  assert(desc.trim(), '=', '[a]->[b]')
})

suite.report()
<commit_msg>add lockfile version verification<commit_after>var {
  skanaar,
  processImports,
  processAsyncImports,
  ImportDepthError,
  renderSvg,
} = require('../dist/nomnoml.js')
var TestSuite = require('./TestSuite.js')

var suite = TestSuite('Misc')
var assert = TestSuite.assert

suite.node_test('assert library version', function () {
  var fs = require('fs')
  var library = require('../dist/nomnoml.js')
  var package = require('../package.json')
  var lockfile = require('../package-lock.json')
  var changelog = fs.readFileSync('changelog.md', { encoding: 'utf-8' })
  var versionMatch = changelog.match(/\d+\.\d+\.\d+/)
  var logVersion = versionMatch && versionMatch[0]
  if (library.version != package.version) {
    throw new Error('version of distribution bundle and npm package must match')
  }
  if (lockfile.version != package.version) {
    throw new Error('version of package-lock and npm package must match')
  }
  var isProductionVersion = !library.version.includes('-')
  if (isProductionVersion && library.version != logVersion) {
    throw new Error('production versions must be documented in changelog')
  }
})

suite.test('skanaar.testsuite.isEqual', function () {
  test(TestSuite.isEqual([1, 2], [1, 2]))
  test(!TestSuite.isEqual([2, 1], [1, 2]))
  test(TestSuite.isEqual({ b: 4, a: 'asdf' }, { a: 'asdf', b: 4 }))
  test(!TestSuite.isEqual({ b: 4, a: 'asdf' }, { a: 'asdf' }))
  test(!TestSuite.isEqual({ a: 'asdf' }, { a: 'asdf', b: 4 }))

  function test(condition) {
    if (!condition) throw new Error('testsuite failure')
  }
})

suite.test('skanaar.testsuite.assert', function () {
  TestSuite.assert([1, 2], '=', [1, 2])
  TestSuite.assert(() => TestSuite.assert([1, 2], '=', [17]), 'throws', Error)
  TestSuite.assert(4, '<', 5)
  TestSuite.assert(() => TestSuite.assert(5, '<', 4), 'throws', Error)
  TestSuite.assert(6, '>', 5)
  TestSuite.assert(() => TestSuite.assert(3, '>', 4), 'throws', Error)
  TestSuite.assert(() => TestSuite.assert(true), 'throws', Error)
})

suite.test('util range', function () {
  assert(skanaar.range([0, 1], 5), '=', [0, 0.25, 0.5, 0.75, 1])
})
suite.test('util sum', function () {
  assert(
    skanaar.sum([[0], [4], [17]], (e) => e[0]),
    '=',
    21
  )
  assert(
    skanaar.sum([[0], [4], [17]], (e) => e[0]),
    '=',
    21
  )
})
suite.test('util find', function () {
  assert(
    skanaar.find([{ a: 0 }, { a: 4, needle: true }, { a: 17 }], (e) => e.a == 4),
    '=',
    { a: 4, needle: true }
  )
})
suite.test('util last', function () {
  assert(skanaar.last([{ a: 0 }, { a: 4 }, { a: 17, needle: true }]), '=', { a: 17, needle: true })
})
suite.test('util hasSubstring', function () {
  assert(skanaar.hasSubstring('xyz abc', 'xyz'), '=', true)
  assert(skanaar.hasSubstring('1 xyz 0', 'xyz'), '=', true)
  assert(skanaar.hasSubstring('abc xyz', 'xyz'), '=', true)
})
suite.test('util indexBy', function () {
  assert(skanaar.indexBy([], 'name'), '=', {})
  assert(skanaar.indexBy([{ name: 'apa' }], 'name'), '=', { apa: { name: 'apa' } })
})
suite.test('util uniqueBy', function () {
  assert(
    skanaar.uniqueBy(
      [
        { a: 4, b: 'x' },
        { a: 17, c: 'y' },
        { a: 4, d: 'z' },
      ],
      'a'
    ),
    '=',
    [
      { a: 4, b: 'x' },
      { a: 17, c: 'y' },
    ]
  )
})

suite.test('processImports resolves shallow imports', function () {
  var mockFiles = {
    importsOneFile: '#import: file1',
    importsTwoFiles: '#import: file1\n#import: file2',
    file1: 'file1-contents',
    file2: 'file2-contents',
  }
  var loadFile = (key) => mockFiles[key]
  var output = processImports(mockFiles['importsOneFile'], loadFile)
  assert(output, '=', 'file1-contents')
  output = processImports(mockFiles['importsTwoFiles'], loadFile)
  assert(output, '=', 'file1-contents\nfile2-contents')
})

suite.test('processImports ignores bad imports', function () {
  var out = processImports('#import: root', (key) => {
    throw new Error('filesystem bug')
  })
  assert(out, '=', '')
})

suite.test('processImports throw on recursive imports', function () {
  TestSuite.assert(
    () => processImports('#import: root', (key) => '#import: root'),
    'throws',
    ImportDepthError
  )
})

var mockFiles = {
  root: `
A
#import: file1
B
#import: file2
C`,
  file1: `
X
#import: file3
Y`,
  file2: 'Q',
  file3: 'W',
}

var mockResult = `
A

X
W
Y
B
Q
C`

suite.test('processImports resolves deep imports', function () {
  var output = processImports(mockFiles['root'], (key) => mockFiles[key])
  assert(output, '=', mockResult)
})

suite.test('processAsyncImports resolves deep imports', async function () {
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
  var output = await processAsyncImports(mockFiles['root'], (key) =>
    delay(1).then(() => mockFiles[key])
  )
  assert(output, '=', mockResult)
})

suite.node_test('processAsyncImports via file system', async function () {
  var fs = require('fs')
  var output = await processAsyncImports(
    fs.readFileSync('test/import-test.nomnoml', 'utf-8'),
    (key) => fs.promises.readFile('test/' + key, 'utf-8')
  )
  fs.writeFileSync('test/output.async.nomnoml', output)
})

suite.test('escape [data-name] attribute value in SVG', function () {
  var output = renderSvg('[&]-[<]')
  assert(output, 'includes', 'data-name="&"')
})

suite.test('SVG export includes nomnoml source in <desc>', function () {
  var output = renderSvg('[a]->[b]')
  var desc = output.match('<desc ?>(.*)</desc>')[1]
  assert(desc.trim(), '=', '[a]->[b]')
})

suite.report()
<|endoftext|>


<commit_before>'use strict';

var chai = chai || require('chai');
var bitcore = bitcore || require('../bitcore');
var coinUtil = bitcore.util;
var should = chai.should();
var buffertools = require('buffertools');

describe('util', function() {
  describe('exist', function() {
    it('should initialze the util object', function() {
      should.exist(bitcore.util);
    });
  });
  describe('#parseValue', function() {
    it('should convert floating points to satoshis correctly', function() {
      function test_value(datum) {
        var decimal = datum[0];
        var intStr = datum[1];
        var bn = coinUtil.parseValue(decimal);
        should.exist(bn);
        bn.toString().should.equal(intStr);
      }
      var dataValues = [
        ['0', '0'],
        ['1.0', '100000000'],
        ['0.1', '10000000'],
        ['.1', '10000000'],
        ['0.0005', '50000'],
        ['.000000001', '0'],
        ['.000000009', '0'],
        ['.00000000000000001', '0']
      ];
      dataValues.forEach(function(datum) {
        test_value(datum);
      });
    });
  });
  describe('#ripe160', function() {
    var pk = 'a5c756101065ac5b8f689139e6d856fa99e54b5000b6428b43729d334cc9277d';
    it('should work for ' + pk, function() {
      var pubKeyHash = coinUtil.ripe160(new Buffer(pk, 'hex'));
      var pkh = buffertools.toHex(pubKeyHash);
      pkh.should.equal('d166a41f27fd4b158f70314e5eee8998bf3d97d5');
    });
  });

  describe('#sha256', function() {
    var pk = '03d95e184cce34c3cfa58e9a277a09a7c5ed1b2a8134ea1e52887bc66fa3f47071'
    it('should work for ' + pk, function() {
      var pubKeyHash = coinUtil.sha256(pk);
      var pkh = buffertools.toHex(pubKeyHash);
      pkh.should.equal('a5c756101065ac5b8f689139e6d856fa99e54b5000b6428b43729d334cc9277d');
    });
  });

  describe('#twoSha256', function() {
    var data = new Buffer('907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000000fd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de80200000000ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000000599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec2291fe51c6f', 'hex');
    it('should work for ' + data, function() {
      var twoSha256 = buffertools.toHex(buffertools.reverse(coinUtil.twoSha256(data)));
      var expected = '31af167a6cf3f9d5f6875caa4d31704ceb0eba078d132b78dab52c3b8997317e';
      twoSha256.should.equal(expected);
    });
  });

  describe('#sha256ripe160', function() {
    var pk = '03d95e184cce34c3cfa58e9a277a09a7c5ed1b2a8134ea1e52887bc66fa3f47071'
    it('should work for ' + pk, function() {
      var pubKeyHash = coinUtil.sha256ripe160(pk);
      var pkh = buffertools.toHex(pubKeyHash);
      pkh.should.equal('d166a41f27fd4b158f70314e5eee8998bf3d97d5');
    });
  });
  describe('#ripemd160', function() {
    var ripemdData = [
      ['somemessage123', '12fd01a7ec6b9ba23b3a5c16fbfab3ac19624a88'],
      ['', '9c1185a5c5e9fc54612808977ee8f548b2258d31'],
      ['0000', 'ab20e58c9eeb4776e719deff3158e26ca9edb636']
    ];
    ripemdData.forEach(function(datum) {
      it('should work for ' + datum[0], function() {
        var r = coinUtil.ripe160(new bitcore.Buffer(datum[0]));
        buffertools.toHex(r).should.equal(datum[1]);
      });
      it('should work for Buffer ' + datum[0], function() {
        var r = coinUtil.ripe160(new Buffer(datum[0]));
        buffertools.toHex(r).should.equal(datum[1]);
      });
    });
  });
  describe('#intToBuffer2C', function() {
    var data = [
      [0, ''],
      [-0, ''],
      [1, '01'],
      [-1, 'ff'],
      [18, '12'],
      [-18, 'ee'],
      [127, '7f'],
      [128, '8000'],
      [129, '8100'],
      [4096, '0010'],
      [-4096, '00f0'],
      [32767, 'ff7f'],
      [878082192, '90785634'],
      [0x01234567890, '9078563412'],
      [4294967295, 'ffffffff00'],
      [4294967296, '0000000001'],
      [4294967297, '0100000001'],
      [2147483647, 'ffffff7f'],
      [-2147483647, '01000080'],
    ];
    data.forEach(function(datum) {
      var integer = datum[0];
      var result = datum[1];
      it('should work for ' + integer, function() {
        var buf = coinUtil.intToBuffer2C(integer);
        var hex = buffertools.toHex(buf);
        hex.should.equal(result);
      });
    });
  });
  describe('#varIntBuf', function() {
    var data = [
      [0, '00'],
      [1, '01'],
      [253, 'fdfd00'],
      [254, 'fdfe00'],
      [255, 'fdff00'],
      [0x100, 'fd0001'],
      [0x1000, 'fd0010'],
      [0x1001, 'fd0110'],
      [0x10000, 'fe00000100'],
      [0x12345, 'fe45230100'],
      [0x12345678, 'fe78563412'],
      [0x123456789a, 'ff9a78563412000000'],
      [0x123456789abcde, 'ffdebc9a7856341200'],
    ];
    data.forEach(function(datum) {
      var integer = datum[0];
      var result = datum[1];
      it('should work for ' + integer, function() {
        buffertools.toHex(coinUtil.varIntBuf(integer)).should.equal(result);
      });
    });
  });
  describe('#getVarIntSize', function() {
    var data = [
      [0, 1],
      [1, 1],
      [252, 1],
      [253, 3],
      [254, 3],
      [0x100, 3],
      [0x1000, 3],
      [0x1001, 3],
      [0x10000, 5],
      [0x10001, 5],
      [0xffffffff, 5],
      [0x100000000, 9],
      [0x100000001, 9],
    ];
    data.forEach(function(datum) {
      var integer = datum[0];
      var result = datum[1];
      it('should work for ' + integer, function() {
        coinUtil.getVarIntSize(integer).should.equal(result);
      });
    });
  });
  describe('#intToBufferSM', function() {
    var data = [
      [0, ''],
      [1, '01'],
      [-1, '81'],
      [2, '02'],
      [-2, '82'],
      [-32768, '008080'],
    ];
    data.forEach(function(datum) {
      var i = datum[0];
      var hex = datum[1];
      it('should work for ' + i, function() {
        var result = coinUtil.intToBufferSM(i);
        buffertools.toHex(result).should.equal(hex);
      });
    });
  });
  describe('#calcDifficulty', function() {
    var bitsgenesis = 486604799;
    it('should work for ' + bitsgenesis, function() {
      var difficulty = coinUtil.calcDifficulty(bitsgenesis);
      difficulty.should.equal(1);
    });
    var bitslater = 419476394;
    it('should work for ' + bitslater, function() {
      var difficulty = coinUtil.calcDifficulty(bitslater);
      difficulty.should.equal(6119726089);
    });
  });

});
<commit_msg>updated the calcDifficulty test to make it clear with what we're testing<commit_after>'use strict';

var chai = chai || require('chai');
var bitcore = bitcore || require('../bitcore');
var coinUtil = bitcore.util;
var should = chai.should();
var buffertools = require('buffertools');

describe('util', function() {
  describe('exist', function() {
    it('should initialze the util object', function() {
      should.exist(bitcore.util);
    });
  });
  describe('#parseValue', function() {
    it('should convert floating points to satoshis correctly', function() {
      function test_value(datum) {
        var decimal = datum[0];
        var intStr = datum[1];
        var bn = coinUtil.parseValue(decimal);
        should.exist(bn);
        bn.toString().should.equal(intStr);
      }
      var dataValues = [
        ['0', '0'],
        ['1.0', '100000000'],
        ['0.1', '10000000'],
        ['.1', '10000000'],
        ['0.0005', '50000'],
        ['.000000001', '0'],
        ['.000000009', '0'],
        ['.00000000000000001', '0']
      ];
      dataValues.forEach(function(datum) {
        test_value(datum);
      });
    });
  });
  describe('#ripe160', function() {
    var pk = 'a5c756101065ac5b8f689139e6d856fa99e54b5000b6428b43729d334cc9277d';
    it('should work for ' + pk, function() {
      var pubKeyHash = coinUtil.ripe160(new Buffer(pk, 'hex'));
      var pkh = buffertools.toHex(pubKeyHash);
      pkh.should.equal('d166a41f27fd4b158f70314e5eee8998bf3d97d5');
    });
  });

  describe('#sha256', function() {
    var pk = '03d95e184cce34c3cfa58e9a277a09a7c5ed1b2a8134ea1e52887bc66fa3f47071'
    it('should work for ' + pk, function() {
      var pubKeyHash = coinUtil.sha256(pk);
      var pkh = buffertools.toHex(pubKeyHash);
      pkh.should.equal('a5c756101065ac5b8f689139e6d856fa99e54b5000b6428b43729d334cc9277d');
    });
  });

  describe('#twoSha256', function() {
    var data = new Buffer('907c2bc503ade11cc3b04eb2918b6f547b0630ab569273824748c87ea14b0696526c66ba740200000000fd1f9bdd4ef073c7afc4ae00da8a66f429c917a0081ad1e1dabce28d373eab81d8628de80200000000ad042b5f25efb33beec9f3364e8a9139e8439d9d7e26529c3c30b6c3fd89f8684cfd68ea0200000000599ac2fe02a526ed040000000008535300516352515164370e010000000003006300ab2ec2291fe51c6f', 'hex');
    it('should work for ' + data, function() {
      var twoSha256 = buffertools.toHex(buffertools.reverse(coinUtil.twoSha256(data)));
      var expected = '31af167a6cf3f9d5f6875caa4d31704ceb0eba078d132b78dab52c3b8997317e';
      twoSha256.should.equal(expected);
    });
  });

  describe('#sha256ripe160', function() {
    var pk = '03d95e184cce34c3cfa58e9a277a09a7c5ed1b2a8134ea1e52887bc66fa3f47071'
    it('should work for ' + pk, function() {
      var pubKeyHash = coinUtil.sha256ripe160(pk);
      var pkh = buffertools.toHex(pubKeyHash);
      pkh.should.equal('d166a41f27fd4b158f70314e5eee8998bf3d97d5');
    });
  });
  describe('#ripemd160', function() {
    var ripemdData = [
      ['somemessage123', '12fd01a7ec6b9ba23b3a5c16fbfab3ac19624a88'],
      ['', '9c1185a5c5e9fc54612808977ee8f548b2258d31'],
      ['0000', 'ab20e58c9eeb4776e719deff3158e26ca9edb636']
    ];
    ripemdData.forEach(function(datum) {
      it('should work for ' + datum[0], function() {
        var r = coinUtil.ripe160(new bitcore.Buffer(datum[0]));
        buffertools.toHex(r).should.equal(datum[1]);
      });
      it('should work for Buffer ' + datum[0], function() {
        var r = coinUtil.ripe160(new Buffer(datum[0]));
        buffertools.toHex(r).should.equal(datum[1]);
      });
    });
  });
  describe('#intToBuffer2C', function() {
    var data = [
      [0, ''],
      [-0, ''],
      [1, '01'],
      [-1, 'ff'],
      [18, '12'],
      [-18, 'ee'],
      [127, '7f'],
      [128, '8000'],
      [129, '8100'],
      [4096, '0010'],
      [-4096, '00f0'],
      [32767, 'ff7f'],
      [878082192, '90785634'],
      [0x01234567890, '9078563412'],
      [4294967295, 'ffffffff00'],
      [4294967296, '0000000001'],
      [4294967297, '0100000001'],
      [2147483647, 'ffffff7f'],
      [-2147483647, '01000080'],
    ];
    data.forEach(function(datum) {
      var integer = datum[0];
      var result = datum[1];
      it('should work for ' + integer, function() {
        var buf = coinUtil.intToBuffer2C(integer);
        var hex = buffertools.toHex(buf);
        hex.should.equal(result);
      });
    });
  });
  describe('#varIntBuf', function() {
    var data = [
      [0, '00'],
      [1, '01'],
      [253, 'fdfd00'],
      [254, 'fdfe00'],
      [255, 'fdff00'],
      [0x100, 'fd0001'],
      [0x1000, 'fd0010'],
      [0x1001, 'fd0110'],
      [0x10000, 'fe00000100'],
      [0x12345, 'fe45230100'],
      [0x12345678, 'fe78563412'],
      [0x123456789a, 'ff9a78563412000000'],
      [0x123456789abcde, 'ffdebc9a7856341200'],
    ];
    data.forEach(function(datum) {
      var integer = datum[0];
      var result = datum[1];
      it('should work for ' + integer, function() {
        buffertools.toHex(coinUtil.varIntBuf(integer)).should.equal(result);
      });
    });
  });
  describe('#getVarIntSize', function() {
    var data = [
      [0, 1],
      [1, 1],
      [252, 1],
      [253, 3],
      [254, 3],
      [0x100, 3],
      [0x1000, 3],
      [0x1001, 3],
      [0x10000, 5],
      [0x10001, 5],
      [0xffffffff, 5],
      [0x100000000, 9],
      [0x100000001, 9],
    ];
    data.forEach(function(datum) {
      var integer = datum[0];
      var result = datum[1];
      it('should work for ' + integer, function() {
        coinUtil.getVarIntSize(integer).should.equal(result);
      });
    });
  });
  describe('#intToBufferSM', function() {
    var data = [
      [0, ''],
      [1, '01'],
      [-1, '81'],
      [2, '02'],
      [-2, '82'],
      [-32768, '008080'],
    ];
    data.forEach(function(datum) {
      var i = datum[0];
      var hex = datum[1];
      it('should work for ' + i, function() {
        var result = coinUtil.intToBufferSM(i);
        buffertools.toHex(result).should.equal(hex);
      });
    });
  });
  describe('#calcDifficulty', function() {
    var bitsgenesis = 486604799;
    it('should work for the bits from the genesis block; ' + bitsgenesis, function() {
      var difficulty = coinUtil.calcDifficulty(bitsgenesis);
      difficulty.should.equal(1);
    });
    var randomotherbits = 419476394;
    it('should work for the bits in a randomly chosen block, eg [00000000000000001fef2bbc6da9b65e16f9187b7d88f15a308490bf2c9b8e1d] ' + randomotherbits, function() {
      var difficulty = coinUtil.calcDifficulty(randomotherbits);
      difficulty.should.equal(6119726089);
    });
  });

});
<|endoftext|>


<commit_before>"use strict";
//Define the modules required to mocha testing
const assert = require("chai").assert;
const http = require ("http");
const expect = require("chai").expect;
const should = require("should");
const aws = require ("aws-sdk");
const dynamodbLocal = require("../index.js");

aws.config.update({ accessKeyId: "localAccessKey", secretAccessKey: "localSecretAccessKey", region: "localRegion"});
var db = new aws.DynamoDB({ endpoint: 'http://localhost:8000' });

describe("Check Table operations", function() {
  describe("#update table", function(){
    this.timeout(50000);
    it("should update the table", function(done){
      var params = {
          "AttributeDefinitions": [
              {
                "AttributeName": "batch",
                "AttributeType": "N"
              }
          ],
          "ProvisionedThroughput": {
              "ReadCapacityUnits": 10,
              "WriteCapacityUnits": 10
          },
          "TableName": "Movies"
        }

    db.updateTable(params,function(err,data){
        if (err){
            should.not.exist(data);
        } else {
            data.TableDescription.should.have.property("TableName","Movies");
            should.exist(data);
        }
        done();
      });
    });
  });
  describe("queue-handler", function() {
    this.timeout(50000);
    it("should connect to dynamodb and list tables", function(done) {
      db.listTables({}, function(err, data) {
        if(err){
            should.exist(err);
        } else {
            should.not.exist(data);
        }
        done();
        });
    });
  });

  describe("#getItems", function() {
    this.timeout(50000);
    var tableDes = db.getItem({"TableName": "Movies"})
    it("Retrieve hostname from created tables", function() {
      assert.equal(tableDes.httpRequest.endpoint.hostname,"localhost");
    });

    it("Retrieves the path of the table",function(){
      assert.equal(tableDes.httpRequest.path, "/");
    });
  });
});
<commit_msg>Fixed unit test where tables array does exist, but it is empty.<commit_after>"use strict";
//Define the modules required to mocha testing
const assert = require("chai").assert;
const http = require ("http");
const expect = require("chai").expect;
const should = require("should");
const aws = require ("aws-sdk");
const dynamodbLocal = require("../index.js");

aws.config.update({ accessKeyId: "localAccessKey", secretAccessKey: "localSecretAccessKey", region: "localRegion"});
var db = new aws.DynamoDB({ endpoint: 'http://localhost:8000' });

describe("Check Table operations", function() {
  describe("#update table", function(){
    this.timeout(50000);
    it("should update the table", function(done){
      var params = {
          "AttributeDefinitions": [
              {
                "AttributeName": "batch",
                "AttributeType": "N"
              }
          ],
          "ProvisionedThroughput": {
              "ReadCapacityUnits": 10,
              "WriteCapacityUnits": 10
          },
          "TableName": "Movies"
        }

    db.updateTable(params,function(err,data){
        if (err){
            should.not.exist(data);
        } else {
            data.TableDescription.should.have.property("TableName","Movies");
            should.exist(data);
        }
        done();
      });
    });
  });
  describe("queue-handler", function() {
    this.timeout(50000);
    it("should connect to dynamodb and list tables", function(done) {
      db.listTables({}, function(err, data) {
        if(err){
            should.exist(err);
        } else {
            expect(data.TableNames).to.be.empty;
        }
        done();
        });
    });
  });

  describe("#getItems", function() {
    this.timeout(50000);
    var tableDes = db.getItem({"TableName": "Movies"})
    it("Retrieve hostname from created tables", function() {
      assert.equal(tableDes.httpRequest.endpoint.hostname,"localhost");
    });

    it("Retrieves the path of the table",function(){
      assert.equal(tableDes.httpRequest.path, "/");
    });
  });
});
<|endoftext|>


<commit_before>import { Meteor } from "meteor/meteor";
import { DDP } from "meteor/ddp-client";

/** TODO:

    - make changeset persistent, so we don't lose the changes if we
      need to stop the app before being able to reconnect?

 */

/** Server code */
export default class ServerSyncClient {

  /** establish the connection and setup onReconnect handler */
  constructor(URL, onConnected) {
    const self = this;

    this._initialized = false;
    this._connection = DDP.connect(URL);
    this._connection.onReconnect = function() {
      console.log("reconnected");

      if (!this._initialized) {
        onConnected && onConnected();
        this._initialized = true;
      } else {
        // Note: on reconnect, Meteor will perform a complete refresh of
        // the remote collections, i.e., it will remove all items and
        // re-add them. This also means that DDP.connect + subscribe are
        // only useful for fairly small collections (in terms of bytes).
        self._rescheduleSyncDirty();
      }
    }

    // objects of the form { remote: .. , local: .., subscription: ..,
    // options: .. }
    this._collections = {};

    // whether or not the initial sync is completed
    this._ready = false;

    // -- sync logging: this is needed in order to break the cycle.
    // ids that have just been updated, i.e., do not sync
    this._changeSets = { remote: {}, local: {} };
  }

  /** schedule a syncDirty for some time from now, if it is already
      scheduled, reschedule. Each remote reschedules this. This way we
      won't syncDirty while changes are still coming in. TODO: This is
      a bit of a #hack. It would be better to have a signal when the
      sync from server is all caught up.
  */
  _rescheduleSyncDirty() {
    const self = this;
    if (this._syncDirtyTimeout) {
      Meteor.clearTimeout(this._syncDirtyTimeout);
    }
    this._syncDirtyTimeout = Meteor.setTimeout(function() {
      self._syncDirty();
    }, 2000);
  }

  /** Subscribe to the given remote collection, creating a synced
      local copy of the result set for the given query.
      @param collectionName: name of publication to sync with
      @param options: an object containing:
      - mode: "write" (default), or "read"
      - args: subscription arguments (as given to Meteor.subscribe(.., args))
  */
  sync(collectionName, options = {mode: "write", args: []}) {
    const self = this;

    const query = options.query || {};

    this._connection.reconnect();

    // check(collectionName, String);
    if (_.has(this._collections, collectionName)) {
      console.log("already subscribed to", collectionName);
      return;
    }

    this._collections[collectionName] = {
      options: options
    };

    // remote collection
    const remoteCollection =
      new Mongo.Collection(collectionName, this._connection);
    this._collections[collectionName].remote = remoteCollection;
    // local collection
    let localCollection = null;
    if (options.collection) {
      localCollection = options.collection;
    } else {
      localCollection = new Mongo.Collection(collectionName);
    }
    // always clean the local collection before joining the sync:
    localCollection.remove({});
    this._collections[collectionName].local = localCollection;

    // add subscription arguments
    let args = options.args || [];
    args.unshift(collectionName);
    args.push({
      onReady: function() {
        console.log("onReady", collectionName);
        self._ready = true;
        if (options.onReady) {
          options.onReady();
        }

        collectionName && self._syncDirty(collectionName);
      },
      onError: function(e) {
        console.log("onError", e);
      },
      onStop: function(e) {
        console.log("onStop", e);
      }
    });
    const subscription = this._connection.subscribe.apply(this._connection, args);
    this._collections[collectionName].subscription = subscription;

    // ---------------------------------------------------------

    // sync down (from remote to local)
    // each remote change resets the timer for syncDirty
    remoteCollection.find(query).observeChanges({
      added(id, fields) {
        if (self._changeSets.local[id]) {
          // this remote addition just confirms the local addition
        } else {
          if (self._syncDirtyTimeout) {
            self._rescheduleSyncDirty();
          }
          var obj = fields;
          obj._id = id;
          self._changeSets.remote[id] = true;
          localCollection.upsert(id, obj);
          console.log("added by remote");
        }
        delete self._changeSets.local[id];
      },

      changed(id, fields) {
        if (self._changeSets.local[id]
            && self._changeSets.local[id]._synced) {
          // remote confirmed the update
          console.log("ignoring remote update");
        } else {
          if (self._syncDirtyTimeout) {
            self._rescheduleSyncDirty();
          }

          var obj = fields;
          obj._id = id;
          self._changeSets.remote[id] = true;
          // remote changes invalidates local changes:
          if (localCollection.findOne(id)) {
            localCollection.upsert(id, {$set: obj});
          } else {
            // document was removed locally (presumably while
            // offline), recreate it
            localCollection.insert(remoteCollection.findOne(id));
          }
          console.log("changed by remote");
        }
        delete self._changeSets.local[id];
      },

      removed(id) {
        if (self._changeSets.local[id]
            && self._changeSets.local[id]._synced) {
          // remote confirmed removal
          console.log("ignoring remote removal");
        } else {
          if (self._syncDirtyTimeout) {
            self._rescheduleSyncDirty();
          }

          self._changeSets.remote[id] = true;
          // remote changes invalidates local changes:
          console.log("removed by remote");
          localCollection.remove(id);
        }
        delete self._changeSets.local[id];
      }
    });

    // ---------------------------------------------------------

    // sync up (from local to remote)
    if (options.mode != "read") {
      localCollection.find().observeChanges({

        added(id, fields) {
          if (!self._changeSets.remote[id]) {
            // this addition was not initiated by remote; sync up remote
            self._changeSets.local[id] = {
              collectionName: collectionName
            };
            var obj = fields;
            obj._id = id;

            // only sync back up after initial sync down
            if (self._ready) {
              if (self._connection.status().connected) {
                self._changeSets.local[id]._synced = true;
                remoteCollection.upsert(id, obj);
                console.log("added to remote");
              } else {
                // can't sync this right now, add to change set
                console.log("insert queued until reconnect", id);
                self._changeSets.local[id].obj = obj;
                self._changeSets.local[id].action = "insert";
              }
            }
          } else {
            // acknowledged, clear flag for next update
            delete self._changeSets.remote[id];
          }
        },

        changed(id, fields) {
          if (!self._changeSets.remote[id]) {
            // this change was not initiated by remote; sync up remote
            if (!self._changeSets.local[id]) {
              self._changeSets.local[id] = {
                collectionName: collectionName
              };
            }

            var obj = fields;
            if (self._ready) {
              if (self._connection.status().connected) {
                // obj._updated = Date.now();
                // delete obj._dirty;
                self._changeSets.local[id]._synced = true;
                remoteCollection.upsert(id, {$set: obj});
                console.log("changed in remote");
              } else {
                // localCollection.update(id, {$set: {"_dirty": true}});
                // console.warn("cannot reach server, not updating;",
                // "local change will be lost on reconnect");
                console.log("update queued until reconnect", id);
                if (!self._changeSets.local[id].obj) {
                  self._changeSets.local[id].obj = {};
                }
                _.extend(self._changeSets.local[id].obj, obj);
                self._changeSets.local[id].action = "update"; // may overwrite "insert"
              }
            }
          } else {
            // else either self was just changed remotely, or it was
            // marked dirty by offline insert locally; don't do anything

            // acknowledged, clear flag for next update
            delete self._changeSets.remote[id];
          }
        },

        removed(id) {
          if (!self._changeSets.remote[id]) {
            // this removal was not initiated by remote; sync up remote
            self._changeSets.local[id] = {
              collectionName: collectionName
            }; // this will overwrite any updates or inserts, but that's OK

            if (self._ready) {
              if (self._connection.status().connected) {
                self._changeSets.local[id]._synced = true;
                remoteCollection.remove(id);
                console.log("removed in remote");
              } else {
                // queue up for remote deletion
                // self._locallyDeleted.push({
                //   collectionName: collectionName,
                //   id: id
                // });
                // console.warn("cannot reach server, not removing;",
                // "local change will be lost on reconnect");
                console.log("removal queued until reconnect", id);
                self._changeSets.local[id].action = "remove";
              }
            }
          } else {
            // acknowledged, clear flag for next update
            delete self._changeSets.remote[id];
          }
        }
      });
    }

  }

  /** sync dirty things, but only if not changed remotely */
  _syncDirty(collectionName) {
    if (this._syncDirtyTimeout) {
      Meteor.clearTimeout(this._syncDirtyTimeout);
      this._syncDirtyTimeout = null;
    }

    if (!this._connection.status().connected) {
      console.warn("syncDirty failed;",
                   "lost connection again before we could sync",
                   this._connection.status());
      return;
    }

    const self = this;

    // wait for heartbeat:
    // console.log("connection", self._connection);
    // const heartbeat = this._connection._heartbeat;
    // if (!heartbeat) {
    //   Meteor.setTimeout(function() {
    //     // console.log("delayed connection", self._connection);
    //     console.log("waiting for master heartbeat", heartbeat);
    //     self._syncDirty(collectionName);
    //   }, 2000);
    // }

    console.log("_syncDirty", self._changeSets.local);
    // console.log("_syncDirty");

    _.each(self._changeSets.local, function(changeInfo, id) {

      if (changeInfo._synced) {
        // why does this happen?
        return;
      }

      changeInfo._synced = true;
      // ^ we need to mark this changeInfo as sync, so we can
      // recognize it when remote confirms it

      // console.log("_syncDirty one", changeInfo, collectionName);
      if (changeInfo.collectionName == collectionName
          || collectionName == undefined) {

        const remoteCollection =
          self._collections[changeInfo.collectionName].remote;

        if (changeInfo.action == "insert") {
          remoteCollection.upsert(id, changeInfo.obj, function(err, res) {
            console.log("insert local -> remote:", id, err, res);
          });
        } else if (changeInfo.action == "update") {
          remoteCollection.upsert(id, {$set: changeInfo.obj}, function(err, res) {
            console.log("update local -> remote:", id, err, res);
          });
        } else if (changeInfo.action == "remove") {
          remoteCollection.remove(id, function(err, res) {
            console.log("removed local -> remote:", id, err, res);
          });
        }
      }
    });

  }

  /** get (local) collection by name */
  getCollection(name) {
    return this._collections[name].local;
  }

};
<commit_msg>Now handling pre-connection changes as well<commit_after>import { Meteor } from "meteor/meteor";
import { DDP } from "meteor/ddp-client";

/** TODO:

    - make changeset persistent, so we don't lose the changes if we
      need to stop the app before being able to reconnect?

 */
var ignore = 0;

/** Server code */
export default class ServerSyncClient {

  /** establish the connection and setup onReconnect handler */
  constructor(URL, onConnected) {
    const self = this;

    this._initialized = false;
    this._connection = DDP.connect(URL);
    this._connection.onReconnect = function() {
      console.log("reconnected");

      if (!self._initialized) {
        onConnected && onConnected();

        // always clean the local collection before joining the sync,
        // otherwise we won't get destructive changes the master made
        // while we weren't running
        _.each(self._collections, function(collectionSet, name) {
          console.log("clearing collection", name);
          ignore = collectionSet.local.remove({});
        });

        self._initialized = true;
      }
      // else {
      self._rescheduleSyncDirty();
      // }
    }

    // We are impatient: forcing a higher rate of reconnection
    // attempts when unable to reach master
    Meteor.setInterval(function() {
      if (self._connection.status().status == "waiting") {
        self._connection.reconnect();
      }
    }, 10000);

    // objects of the form { remote: .. , local: .., subscription: ..,
    // options: .. }
    this._collections = {};

    // whether or not the initial sync is completed
    this._ready = false;

    // -- sync logging: this is needed in order to break the cycle.
    // ids that have just been updated, i.e., do not sync
    this._changeSets = { remote: {}, local: {} };
  }

  /** schedule a syncDirty for some time from now, if it is already
      scheduled, reschedule. Each remote reschedules this. This way we
      won't syncDirty while changes are still coming in. TODO: This is
      a bit of a #hack. It would be better to have a signal when the
      sync from server is all caught up.
  */
  _rescheduleSyncDirty() {
    const self = this;
    if (this._syncDirtyTimeout) {
      Meteor.clearTimeout(this._syncDirtyTimeout);
    }
    this._syncDirtyTimeout = Meteor.setTimeout(function() {
      self._syncDirty();
    }, 2000);
  }

  /** Subscribe to the given remote collection, creating a synced
      local copy of the result set for the given query.
      @param collectionName: name of publication to sync with
      @param options: an object containing:
      - mode: "write" (default), or "read"
      - args: subscription arguments (as given to Meteor.subscribe(.., args))
      - onReady: a callback function for when the subscription becomes
        ready (initial sync is complete)
  */
  sync(collectionName, options = {mode: "write", args: []}) {
    const self = this;

    const query = options.query || {};

    this._connection.reconnect();

    // check(collectionName, String);
    if (_.has(this._collections, collectionName)) {
      console.log("already subscribed to", collectionName);
      return;
    }

    this._collections[collectionName] = {
      options: options
    };

    // remote collection
    const remoteCollection =
      new Mongo.Collection(collectionName, this._connection);
    this._collections[collectionName].remote = remoteCollection;
    // local collection
    let localCollection = null;
    if (options.collection) {
      localCollection = options.collection;
    } else {
      localCollection = new Mongo.Collection(collectionName);
    }
    this._collections[collectionName].local = localCollection;

    // add subscription arguments
    let args = options.args || [];
    args.unshift(collectionName);
    args.push({
      onReady: function() {
        console.log("onReady", collectionName);
        self._ready = true;
        if (options.onReady) {
          options.onReady();
        }

        collectionName && self._syncDirty(collectionName);
      },
      onError: function(e) {
        console.log("onError", e);
      },
      onStop: function(e) {
        console.log("onStop", e);
      }
    });
    const subscription = this._connection.subscribe.apply(this._connection, args);
    this._collections[collectionName].subscription = subscription;
    const collectionSet = this._collections[collectionName];

    // ---------------------------------------------------------

    // sync down (from remote to local)
    // each remote change resets the timer for syncDirty
    remoteCollection.find(query).observeChanges({
      added(id, fields) {
        if (self._changeSets.local[id]) {
          // this remote addition just confirms the local addition
        } else {
          if (self._syncDirtyTimeout) {
            self._rescheduleSyncDirty();
          }
          var obj = fields;
          obj._id = id;
          self._changeSets.remote[id] = true;
          localCollection.upsert(id, obj);
          console.log("added by remote");
        }
        delete self._changeSets.local[id];
      },

      changed(id, fields) {
        if (self._changeSets.local[id]
            && self._changeSets.local[id]._synced) {
          // remote confirmed the update
          console.log("ignoring remote update");
        } else {
          if (self._syncDirtyTimeout) {
            self._rescheduleSyncDirty();
          }

          var obj = fields;
          obj._id = id;
          self._changeSets.remote[id] = true;
          // remote changes invalidates local changes:
          if (localCollection.findOne(id)) {
            if (self._changeSets.local[id]) {
              // we made local changes as well; overwrite the object
              // completely (don't just patch it)
              localCollection.upsert(id, remoteCollection.findOne(id));
            } else {
              localCollection.upsert(id, {$set: obj});
            }
          } else {
            // document was removed locally (presumably while
            // offline), recreate it completely (not just the change)
            localCollection.insert(remoteCollection.findOne(id));
          }
          console.log("changed by remote");
        }
        delete self._changeSets.local[id];
      },

      removed(id) {
        if (self._changeSets.local[id]
            && self._changeSets.local[id]._synced) {
          // remote confirmed removal
          console.log("ignoring remote removal");
        } else {
          if (self._syncDirtyTimeout) {
            self._rescheduleSyncDirty();
          }

          self._changeSets.remote[id] = true;
          // remote changes invalidates local changes:
          console.log("removed by remote");
          localCollection.remove(id);
        }
        delete self._changeSets.local[id];
      }
    });

    // ---------------------------------------------------------

    // sync up (from local to remote)
    if (options.mode != "read") {
      let initialCount = localCollection.find().count();
      localCollection.find().observeChanges({

        added(id, fields) {
          // count initial insertions; these are the insertions that
          // we see on startup of the app, i.e., these are already in
          // the DB and are just being added to the meteor collection
          if (initialCount-- > 0) {
            // ignore them
            return;
          }

          if (!self._changeSets.remote[id]) {
            // this addition was not initiated by remote; sync up remote
            self._changeSets.local[id] = {
              collectionName: collectionName
            };
            var obj = fields;
            obj._id = id;

            // only sync back up after initial sync down
            if (self._ready
                && self._connection.status().connected) {

              self._changeSets.local[id]._synced = true;
              remoteCollection.upsert(id, obj);
              console.log("added to remote");
            } else {
              // can't sync this right now, add to change set
              console.log("insert queued until reconnect", id);
              self._changeSets.local[id].obj = obj;
              self._changeSets.local[id].action = "insert";
            }
          } else {
            // acknowledged, clear flag for next update
            delete self._changeSets.remote[id];
          }
        },

        changed(id, fields) {
          console.log("changed", id);

          if (!self._changeSets.remote[id]) {
            // this change was not initiated by remote; sync up remote
            if (!self._changeSets.local[id]) {
              self._changeSets.local[id] = {
                collectionName: collectionName
              };
            }

            var obj = fields;
            if (self._ready) {
              if (self._connection.status().connected) {
                // obj._updated = Date.now();
                // delete obj._dirty;
                self._changeSets.local[id]._synced = true;
                remoteCollection.upsert(id, {$set: obj});
                console.log("changed in remote");
              } else {
                console.log("update queued until reconnect", id);
                if (!self._changeSets.local[id].obj) {
                  self._changeSets.local[id].obj = {};
                }
                _.extend(self._changeSets.local[id].obj, obj);
                self._changeSets.local[id].action = "update"; // may overwrite "insert"
              }
            } else {
              // we have not yet connected to master, but we have
              // inserted this document earlier (and potentially
              // already updated it, too), so it's OK to edit
              if (self._changeSets.local[id]
                  && self._changeSets.local[id].action ) {
                console.log("updated newly inserted item", id);
                _.extend(self._changeSets.local[id].obj, obj);
                self._changeSets.local[id].action = "update"; // may overwrite "insert"
              }
            }

          } else {
            // else either self was just changed remotely, or it was
            // marked dirty by offline insert locally; don't do anything

            // acknowledged, clear flag for next update
            delete self._changeSets.remote[id];
          }
        },

        removed(id) {
          console.log("removed", id);
          if (ignore-- > 0) {
            // ignore these removals, they are from the cleanup
            return;
          }

          if (!self._changeSets.remote[id]) {
            // this removal was not initiated by remote; sync up remote
            self._changeSets.local[id] = {
              collectionName: collectionName
            }; // this will overwrite any updates or inserts, but that's OK

            if (self._ready) {
              if (self._connection.status().connected) {
                self._changeSets.local[id]._synced = true;
                remoteCollection.remove(id);
                console.log("removed in remote");
              } else {
                console.log("removal queued until reconnect", id);
                self._changeSets.local[id].action = "remove";
              }
            }
          } else {
            // acknowledged, clear flag for next update
            delete self._changeSets.remote[id];
          }
        }
      });
    }

  }

  /** sync dirty things up to master, but only if not changed remotely */
  _syncDirty(collectionName) {
    if (this._syncDirtyTimeout) {
      Meteor.clearTimeout(this._syncDirtyTimeout);
      this._syncDirtyTimeout = null;
    }

    if (!this._connection.status().connected) {
      console.warn("syncDirty failed;",
                   "lost connection again before we could sync",
                   this._connection.status());
      return;
    }

    const self = this;

    // wait for heartbeat:
    // console.log("connection", self._connection);
    // const heartbeat = this._connection._heartbeat;
    // if (!heartbeat) {
    //   Meteor.setTimeout(function() {
    //     // console.log("delayed connection", self._connection);
    //     console.log("waiting for master heartbeat", heartbeat);
    //     self._syncDirty(collectionName);
    //   }, 2000);
    // }

    console.log("_syncDirty", self._changeSets.local);
    // console.log("_syncDirty");

    _.each(self._changeSets.local, function(changeInfo, id) {

      if (changeInfo._synced) {
        // why does this happen?
        return;
      }

      changeInfo._synced = true;
      // ^ we need to mark this changeInfo as sync, so we can
      // recognize it when remote confirms it

      // console.log("_syncDirty one", changeInfo, collectionName);
      if (changeInfo.collectionName == collectionName
          || collectionName == undefined) {

        const remoteCollection =
          self._collections[changeInfo.collectionName].remote;

        if (changeInfo.action == "insert") {
          remoteCollection.upsert(id, changeInfo.obj, function(err, res) {
            console.log("insert local -> remote:", id, err, res);
            const localCollection =
              self._collections[changeInfo.collectionName].local;
            if (!localCollection.findOne(id)) {
              // this item was removed locally, due to initial clean up; re-add it
              localCollection.upsert(id, changeInfo.obj);
            }
          });
        } else if (changeInfo.action == "update") {
          remoteCollection.upsert(id, {$set: changeInfo.obj}, function(err, res) {
            console.log("update local -> remote:", id, err, res);
            const localCollection =
              self._collections[changeInfo.collectionName].local;
            if (!localCollection.findOne(id)) {
              // this item was removed locally, due to initial clean up; re-add it
              localCollection.upsert(id, remoteCollection.findOne(id));
            }
          });
        } else if (changeInfo.action == "remove") {
          remoteCollection.remove(id, function(err, res) {
            console.log("removed local -> remote:", id, err, res);
          });
        }
      }
    });

  }

  /** get (local) collection by name */
  getCollection(name) {
    return this._collections[name].local;
  }

};
<|endoftext|>


<commit_before>var productApp = angular.module('fbeaztAdmin')

productApp.controller('productListCtrl', function ($scope, $http, $routeParams) {
  $scope.stores = []
  $scope.products = []
  $scope.selected_store = null
  $scope.selected_store_name = 'Select Store'

  $scope.reloadProduct = function () {
    if (!$scope.selected_store) return
    $http.get('/api/products/' + $scope.selected_store,
      { params: { "page_size": 200 } })
      .success(function (d) {
        $scope.products = d.items
        $scope.total = d.total
      }).error(function (e) {
        alert(e)
      })
  }

  $scope.reloadStore = function () {
    $http.get('/api/stores', { params: { 'page_size': 200 } }).success(function (d) {
      $scope.stores = d.items
      if ($routeParams.store_id)
        $scope.setStore($routeParams.store_id)
      else if ($scope.stores && $scope.stores.length > 0)
        $scope.setStore($scope.stores[0]._id.$oid)
      if (!$scope.stores || $scope.stores.length == 0) {
        $scope.stores = []
        $scope.stores.push({ '_id': { "$oid": '' }, 'name': 'No stores available!' })
      }

      $scope.reloadProduct()
    }).error(function (e) {
      alert(e)
    })
  }

  $scope.reloadStore()

  $scope.setStore = function (store_id) {
    if (!store_id || store_id == '-1' || store_id == '') return
    $scope.selected_store = store_id

    for (var i = 0; i < $scope.stores.length; ++i) {
      var s = $scope.stores[i]
      if (s._id.$oid == store_id) {
        $scope.selected_store_name = s.name
      }
    }

    $scope.reloadProduct()
    return false
  }

  $scope.deactivateProduct = function (id) {
    if (id && id != "-1") {
      $http.delete('/api/product/' + $scope.selected_store + '/' + id).success(function (d) {
        $scope.reloadProduct()
      }).error(function (e) {
        alert(e)
        $scope.reloadProduct()
      })
    }
    return false
  }

  $scope.activateProduct = function (id) {
    if (id && id != "-1") {
      $http.put('/api/product/activate/' + $scope.selected_store + '/' + id).success(function (d) {
        $scope.reloadProduct()
      }).error(function (e) {
        alert(e)
        $scope.reloadProduct()
      })
    }
    return false
  }
})

productApp.controller('productDetailCtrl', function ($scope, $routeParams, $location, $http, FileUploader) {
  var uploader = $scope.uploader = new FileUploader()

  uploader.filters.push({
    name: 'imageFilter',
    fn: function (item /*{File|FileLikeObject}*/, options) {
      var type = '|' + item.type.slice(item.type.lastIndexOf('/') + 1) + '|';
      return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1;
    }
  });

  uploader.onSuccessItem = function () {
    $location.path('/product');
  };
  uploader.onErrorItem = function (fileItem, response, status, headers) {
    console.info('onErrorItem', fileItem, response, status, headers);
  };

  var id = $routeParams.id || -1;
  $scope.store_id = $routeParams.store_id || -1;

  $scope.model = {};
  $scope.food_types = [{ 'id': 'veg', 'text': 'Vegetarian' },
    { 'id': 'non-veg', 'text': 'Non-Vegetarian' }];

  $http.get('/api/product/' + $scope.store_id + '/' + id).success(function (d) {
    if (!d._id || !d._id.$oid)
      d._id = { "$oid": "-1" };
    $scope.model = d;
    if (!$scope.model.price_table) {
      $scope.model.price_table = [];
    }
  }).error(function (e) {
    alert('Error while fetching product details')
    $location.path('/product')
  });

  $scope.removePriceTable = function (item) {
    var f = _.findIndex($scope.model.price_table, function (x) {
      return x.no === item.no;
    });
    if (f > -1) {
      $scope.model.price_table.splice(f, 1);
    }
    return false;
  };

  $scope.addPriceTable = function (item) {
    $scope.model.price_table.push({
      no: $scope.model.price_table.length + 1,
      description: '',
      price: 0.0
    })
    return false;
  };

  $scope.save = function () {
    if ($scope.frmProduct.$invalid) {
      alert("Form contains invalid data\n\nPlease check the form and submit again")
      return
    }

    var item = angular.copy($scope.model)

    if (item._id.$oid == "-1") {
      item._id = null
      res = $http.post('/api/product/' + $scope.store_id + '/-1', item)
    } else {
      res = $http.put('/api/product/' + $scope.store_id + '/' + item._id.$oid, item)
    }

    res.success(function (data) {
      if (data.status == "success") {
        var product_id = data.data._id.$oid.toString() || $scope.model._id.$oid.toString()
        var url = '/api/upload_product_image/' + product_id
        uploader.url = url
        if (uploader.queue.length > 0) {
          uploader.queue[0].url = url
          uploader.queue[0].upload()
        } else {
          $location.path('/product')
        }
      } else {
        alert(data.message)
      }
    }).error(function (e) {
      alert(e)
      console.log(e)
    })
  }
})
<commit_msg>fix redirect to product listing page after save.<commit_after>var productApp = angular.module('fbeaztAdmin')

productApp.controller('productListCtrl', function ($scope, $http, $routeParams) {
  $scope.stores = []
  $scope.products = []
  $scope.selected_store = null
  $scope.selected_store_name = 'Select Store'

  $scope.reloadProduct = function () {
    if (!$scope.selected_store) return
    $http.get('/api/products/' + $scope.selected_store,
      { params: { "page_size": 200 } })
      .success(function (d) {
        $scope.products = d.items
        $scope.total = d.total
      }).error(function (e) {
        alert(e)
      })
  }

  $scope.reloadStore = function () {
    $http.get('/api/stores', { params: { 'page_size': 200 } }).success(function (d) {
      $scope.stores = d.items
      if ($routeParams.store_id)
        $scope.setStore($routeParams.store_id)
      else if ($scope.stores && $scope.stores.length > 0)
        $scope.setStore($scope.stores[0]._id.$oid)
      if (!$scope.stores || $scope.stores.length == 0) {
        $scope.stores = []
        $scope.stores.push({ '_id': { "$oid": '' }, 'name': 'No stores available!' })
      }

      $scope.reloadProduct()
    }).error(function (e) {
      alert(e)
    })
  }

  $scope.reloadStore()

  $scope.setStore = function (store_id) {
    if (!store_id || store_id == '-1' || store_id == '') return
    $scope.selected_store = store_id

    for (var i = 0; i < $scope.stores.length; ++i) {
      var s = $scope.stores[i]
      if (s._id.$oid == store_id) {
        $scope.selected_store_name = s.name
      }
    }

    $scope.reloadProduct()
    return false
  }

  $scope.deactivateProduct = function (id) {
    if (id && id != "-1") {
      $http.delete('/api/product/' + $scope.selected_store + '/' + id).success(function (d) {
        $scope.reloadProduct()
      }).error(function (e) {
        alert(e)
        $scope.reloadProduct()
      })
    }
    return false
  }

  $scope.activateProduct = function (id) {
    if (id && id != "-1") {
      $http.put('/api/product/activate/' + $scope.selected_store + '/' + id).success(function (d) {
        $scope.reloadProduct()
      }).error(function (e) {
        alert(e)
        $scope.reloadProduct()
      })
    }
    return false
  }
})

productApp.controller('productDetailCtrl', function ($scope, $routeParams, $location, $http, FileUploader) {
  var uploader = $scope.uploader = new FileUploader();
  var id = $routeParams.id || -1;
  var store_id = $routeParams.store_id || -1;
  var returnUrl = '/product?store_id=' + store_id;
  $scope.store_id = store_id;

  uploader.filters.push({
    name: 'imageFilter',
    fn: function (item /*{File|FileLikeObject}*/, options) {
      var type = '|' + item.type.slice(item.type.lastIndexOf('/') + 1) + '|';
      return '|jpg|png|jpeg|bmp|gif|'.indexOf(type) !== -1;
    }
  });

  uploader.onSuccessItem = function () {
    $location.url(returnUrl);
  };
  uploader.onErrorItem = function (fileItem, response, status, headers) {
    console.info('onErrorItem', fileItem, response, status, headers);
  };

  $scope.model = {};
  $scope.food_types = [{ 'id': 'veg', 'text': 'Vegetarian' },
    { 'id': 'non-veg', 'text': 'Non-Vegetarian' }];

  $http.get('/api/product/' + $scope.store_id + '/' + id).success(function (d) {
    if (!d._id || !d._id.$oid)
      d._id = { "$oid": "-1" };
    $scope.model = d;
    if (!$scope.model.price_table) {
      $scope.model.price_table = [];
    }
  }).error(function (e) {
    alert('Error while fetching product details')
    $location.url(returnUrl);
  });

  $scope.removePriceTable = function (item) {
    var f = _.findIndex($scope.model.price_table, function (x) {
      return x.no === item.no;
    });
    if (f > -1) {
      $scope.model.price_table.splice(f, 1);
    }
    return false;
  };

  $scope.addPriceTable = function (item) {
    $scope.model.price_table.push({
      no: $scope.model.price_table.length + 1,
      description: '',
      price: 0.0
    })
    return false;
  };

  $scope.save = function () {
    if ($scope.frmProduct.$invalid) {
      alert("Form contains invalid data\n\nPlease check the form and submit again");
      return;
    }

    var item = angular.copy($scope.model);

    if (item._id.$oid == "-1") {
      item._id = null;
      res = $http.post('/api/product/' + $scope.store_id + '/-1', item);
    } else {
      res = $http.put('/api/product/' + $scope.store_id + '/' + item._id.$oid, item);
    }

    res.success(function (data) {
      if (data.status == "success") {
        var product_id = data.data._id.$oid.toString() || $scope.model._id.$oid.toString();
        var url = '/api/upload_product_image/' + product_id;
        uploader.url = url;
        if (uploader.queue.length > 0) {
          uploader.queue[0].url = url;
          uploader.queue[0].upload();
        } else {
          console.log("save product success!")
          $location.url(returnUrl);
        }
      } else {
        alert(data.message);
      }
    }).error(function (e) {
      alert(e);
      console.log(e);
    })
  };
});
<|endoftext|>


<commit_before>import { Vector3 } from '../math/Vector3';
import { Object3D } from '../core/Object3D';
import { Line } from '../objects/Line';
import { Float32BufferAttribute } from '../core/BufferAttribute';
import { BufferGeometry } from '../core/BufferGeometry';
import { LineBasicMaterial } from '../materials/LineBasicMaterial';

/**
 * @author alteredq / http://alteredqualia.com/
 * @author mrdoob / http://mrdoob.com/
 * @author WestLangley / http://github.com/WestLangley
 */

function DirectionalLightHelper( light, size, overrideColor ) {

	Object3D.call( this );

	this.light = light;
	this.light.updateMatrixWorld();

	this.matrix = light.matrixWorld;
	this.matrixAutoUpdate = false;

	this.overrideColor = overrideColor;

	if ( size === undefined ) size = 1;

	var geometry = new BufferGeometry();
	geometry.addAttribute( 'position', new Float32BufferAttribute( [
		- size,   size, 0,
		  size,   size, 0,
		  size, - size, 0,
		- size, - size, 0,
		- size,   size, 0
	], 3 ) );

	var material = new LineBasicMaterial( { fog: false, color: this.overrideColor } );

	this.lightPlane = new Line( geometry, material );
	this.add( this.lightPlane );

	geometry = new BufferGeometry();
	geometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) );

	this.targetLine = new Line( geometry, material );
	this.add( this.targetLine );

	this.update();

}

DirectionalLightHelper.prototype = Object.create( Object3D.prototype );
DirectionalLightHelper.prototype.constructor = DirectionalLightHelper;

DirectionalLightHelper.prototype.dispose = function () {

	this.lightPlane.geometry.dispose();
	this.lightPlane.material.dispose();
	this.targetLine.geometry.dispose();
	this.targetLine.material.dispose();

};

DirectionalLightHelper.prototype.update = function () {

	var v1 = new Vector3();
	var v2 = new Vector3();
	var v3 = new Vector3();

	return function update() {

		v1.setFromMatrixPosition( this.light.matrixWorld );
		v2.setFromMatrixPosition( this.light.target.matrixWorld );
		v3.subVectors( v2, v1 );

		this.lightPlane.lookAt( v3 );
		if ( ! this.overrideColor ) this.lightPlane.material.color.copy( this.light.color );

		this.targetLine.lookAt( v3 );
		this.targetLine.scale.z = v3.length();
		if ( ! this.overrideColor ) this.targetLine.material.color.copy( this.light.color );

	};

}();

export { DirectionalLightHelper };
<commit_msg>Allow overrideColor to be changed after construction in DirectionalLightHelper<commit_after>import { Vector3 } from '../math/Vector3';
import { Object3D } from '../core/Object3D';
import { Line } from '../objects/Line';
import { Float32BufferAttribute } from '../core/BufferAttribute';
import { BufferGeometry } from '../core/BufferGeometry';
import { LineBasicMaterial } from '../materials/LineBasicMaterial';

/**
 * @author alteredq / http://alteredqualia.com/
 * @author mrdoob / http://mrdoob.com/
 * @author WestLangley / http://github.com/WestLangley
 */

function DirectionalLightHelper( light, size, overrideColor ) {

	Object3D.call( this );

	this.light = light;
	this.light.updateMatrixWorld();

	this.matrix = light.matrixWorld;
	this.matrixAutoUpdate = false;

	this.overrideColor = overrideColor;

	if ( size === undefined ) size = 1;

	var geometry = new BufferGeometry();
	geometry.addAttribute( 'position', new Float32BufferAttribute( [
		- size,   size, 0,
		  size,   size, 0,
		  size, - size, 0,
		- size, - size, 0,
		- size,   size, 0
	], 3 ) );

	var material = new LineBasicMaterial( { fog: false, color: this.overrideColor } );

	this.lightPlane = new Line( geometry, material );
	this.add( this.lightPlane );

	geometry = new BufferGeometry();
	geometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) );

	this.targetLine = new Line( geometry, material );
	this.add( this.targetLine );

	this.update();

}

DirectionalLightHelper.prototype = Object.create( Object3D.prototype );
DirectionalLightHelper.prototype.constructor = DirectionalLightHelper;

DirectionalLightHelper.prototype.dispose = function () {

	this.lightPlane.geometry.dispose();
	this.lightPlane.material.dispose();
	this.targetLine.geometry.dispose();
	this.targetLine.material.dispose();

};

DirectionalLightHelper.prototype.update = function () {

	var v1 = new Vector3();
	var v2 = new Vector3();
	var v3 = new Vector3();

	return function update() {

		v1.setFromMatrixPosition( this.light.matrixWorld );
		v2.setFromMatrixPosition( this.light.target.matrixWorld );
		v3.subVectors( v2, v1 );

		this.lightPlane.lookAt( v3 );
		if ( ! this.overrideColor ) this.lightPlane.material.color.copy( this.light.color );
		else this.lightPlane.material.color.set( this.overrideColor );

		this.targetLine.lookAt( v3 );
		this.targetLine.scale.z = v3.length();
		if ( ! this.overrideColor ) this.targetLine.material.color.copy( this.light.color );
		else this.targetLine.material.color.set( this.overrideColor );

	};

}();

export { DirectionalLightHelper };
<|endoftext|>


<commit_before>var async   = require("async");

var Amount  = require("../src/js/amount").Amount;
var Remote  = require("../src/js/remote").Remote;
var Server  = require("./server").Server;

var config  = require('../src/js/config').load(require('./config'));

var account_dump = function (remote, account, callback) {
  var self = this;

  async.waterfall([
      function (callback) {
        self.what = "Get latest account_root";

        remote
          .request_ledger_entry('account_root')
          .ledger_hash(remote.ledger_hash())
          .account_root("root")
          .on('success', function (r) {
              //console.log("account_root: %s", JSON.stringify(r, undefined, 2));

              callback();
            })
          .on('error', function(m) {
              console.log("error: %s", m);

              buster.assert(false);
              callback();
            })
          .request();
      },
    ], function (error) {
      callback(error);
    });

  // get closed ledger hash
  // get account root
  // construct a json result
  //
};

/**
 * Helper called by test cases to generate a setUp routine.
 *
 * By default you would call this without options, but it is useful to
 * be able to plug options in during development for quick and easy
 * debugging.
 *
 * @example
 *   buster.testCase("Foobar", {
 *     setUp: testutils.build_setup({verbose: true}),
 *     // ...
 *   });
 *
 * @param opts {Object} These options allow quick-and-dirty test-specific
 *   customizations of your test environment.
 * @param opts.verbose {Bool} Enable all debug output (then cover your ears
 *   and run)
 * @param opts.verbose_ws {Bool} Enable tracing in the Remote class. Prints
 *   websocket traffic.
 * @param opts.verbose_server {Bool} Set the -v option when running rippled.
 * @param opts.no_server {Bool} Don't auto-run rippled.
 * @param host {String} Identifier for the host configuration to be used.
 */
var build_setup = function (opts, host) {
  opts = opts || {};

  // Normalize options
  if (opts.verbose) {
    opts.verbose_ws = true;
    opts.verbose_server = true;
  };

  return function (done) {
    var self = this;

    host = host || config.server_default;

    this.store = this.store || {};

    var data = this.store[host] = this.store[host] || {};

    data.opts = opts;

    async.series([
      function runServerStep(callback) {
        if (opts.no_server) return callback();

        data.server = Server
                        .from_config(host, !!opts.verbose_server)
                        .on('started', callback)
                        .on('exited', function () {
                            // If know the remote, tell it server is gone.
                            if (self.remote)
                              self.remote.server_fatal();
                          })
                        .start();
      },
      function connectWebsocketStep(callback) {
        self.remote = data.remote =
          Remote
            .from_config(host, !!opts.verbose_ws)
            .once('ledger_closed', callback)
            .connect();
      }
    ], done);
  };
};

/**
 * Generate tearDown routine.
 *
 * @param host {String} Identifier for the host configuration to be used.
 */
var build_teardown = function (host) {

  return function (done) {
    host = host || config.server_default;

    var data = this.store[host];
    var opts = data.opts;

    async.series([
      function disconnectWebsocketStep(callback) {

        data.remote
          .on('disconnected', callback)
          .connect(false);
      },
      function stopServerStep(callback) {
        if (opts.no_server)
        {
          return callback();
        }

        data.server
          .on('stopped', callback)
          .stop();
      }
    ], done);
  };
};

var create_accounts = function (remote, src, amount, accounts, callback) {
  assert(5 === arguments.length);

  remote.set_account_seq(src, 1);

  async.forEach(accounts, function (account, callback) {
    // Cache the seq as 1.
    // Otherwise, when other operations attempt to opperate async against the account they may get confused.
    remote.set_account_seq(account, 1);

    remote.transaction()
      .payment(src, account, amount)
      .on('proposed', function (m) {
          // console.log("proposed: %s", JSON.stringify(m));

          callback(m.result != 'tesSUCCESS');
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .submit();
    }, callback);
};

var credit_limit = function (remote, src, amount, callback) {
  assert(4 === arguments.length);

  var _m      = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/);
  if (!_m) {
    console.log("credit_limit: parse error: %s", amount);

    callback('parse_error');
  }
  else
  {
    // console.log("credit_limit: parsed: %s", JSON.stringify(_m, undefined, 2));
    var _account_limit  = _m[1];
    var _quality_in     = _m[2];
    var _quality_out    = _m[3];

    remote.transaction()
      .ripple_line_set(src, _account_limit, _quality_in, _quality_out)
      .on('proposed', function (m) {
          // console.log("proposed: %s", JSON.stringify(m));

          callback(m.result != 'tesSUCCESS');
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .submit();
  }
};

var verify_limit = function (remote, src, amount, callback) {
  assert(4 === arguments.length);

  var _m      = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/);
  if (!_m) {
    // console.log("credit_limit: parse error: %s", amount);

    callback('parse_error');
  }
  else
  {
    // console.log("verify_limit: parsed: %s", JSON.stringify(_m, undefined, 2));
    var _account_limit  = _m[1];
    var _quality_in     = _m[2];
    var _quality_out    = _m[3];

    var _limit          = Amount.from_json(_account_limit);

    remote.request_ripple_balance(src, _limit.issuer().to_json(), _limit.currency().to_json(), 'CURRENT')
      .once('ripple_state', function (m) {
          buster.assert(m.account_limit.equals(_limit));
          buster.assert('undefined' === _quality_in || m.account_quality_in == _quality_in);
          buster.assert('undefined' === _quality_out || m.account_quality_out == _quality_out);

          callback();
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .request();
  }
};

var credit_limits = function (remote, balances, callback) {
  assert(3 === arguments.length);

  var limits = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      limits.push( { "source" : src, "amount" : values[index] } );
    }
  }

  async.every(limits,
    function (limit, callback) {
      credit_limit(remote, limit.source, limit.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

var ledger_close = function (remote, callback) {
  remote.once('ledger_closed', function (m) { callback(); }).ledger_accept();
}

var payment = function (remote, src, dst, amount, callback) {
  assert(5 === arguments.length);

  remote.transaction()
    .payment(src, dst, amount)
    .on('proposed', function (m) {
        // console.log("proposed: %s", JSON.stringify(m));

        callback(m.result != 'tesSUCCESS');
      })
    .on('error', function (m) {
        // console.log("error: %s", JSON.stringify(m));

        callback(m);
      })
    .submit();
};

var payments = function (remote, balances, callback) {
  assert(3 === arguments.length);

  var sends = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      var amount_json = values[index];
      var amount      = Amount.from_json(amount_json);

      sends.push( { "source" : src, "destination" : amount.issuer().to_json(), "amount" : amount_json } );
    }
  }

  async.every(sends,
    function (send, callback) {
      payment(remote, send.source, send.destination, send.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

var transfer_rate = function (remote, src, billionths, callback) {
  assert(4 === arguments.length);

  remote.transaction()
    .account_set(src)
    .transfer_rate(billionths)
    .on('proposed', function (m) {
        // console.log("proposed: %s", JSON.stringify(m));

        callback(m.result != 'tesSUCCESS');
      })
    .on('error', function (m) {
        // console.log("error: %s", JSON.stringify(m));

        callback(m);
      })
    .submit();
};

var verify_balance = function (remote, src, amount_json, callback) {
  assert(4 === arguments.length);
  var amount_req  = Amount.from_json(amount_json);

  if (amount_req.is_native()) {
    remote.request_account_balance(src, 'CURRENT')
      .once('account_balance', function (amount_act) {
          if (!amount_act.equals(amount_req, true)) {
            console.log("verify_balance: failed: %s / %s",
              amount_act.to_text_full(),
              amount_req.to_text_full());
          }

          callback(!amount_act.equals(amount_req, true));
        })
      .request();
  }
  else {
    remote.request_ripple_balance(src, amount_req.issuer().to_json(), amount_req.currency().to_json(), 'CURRENT')
      .once('ripple_state', function (m) {
        // console.log("BALANCE: %s", JSON.stringify(m));
        // console.log("account_balance: %s", m.account_balance.to_text_full());
        // console.log("account_limit: %s", m.account_limit.to_text_full());
        // console.log("issuer_balance: %s", m.issuer_balance.to_text_full());
        // console.log("issuer_limit: %s", m.issuer_limit.to_text_full());

          var account_balance = Amount.from_json(m.account_balance);

          if (!account_balance.equals(amount_req, true)) {
            console.log("verify_balance: failed: %s vs %s / %s: %s",
                        src,
                        account_balance.to_text_full(),
                        amount_req.to_text_full(),
                        account_balance.not_equals_why(amount_req, true));
          }

          callback(!account_balance.equals(amount_req, true));
        })
      .request();
  }
};

var verify_balances = function (remote, balances, callback) {
  var tests = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      tests.push( { "source" : src, "amount" : values[index] } );
    }
  }

  async.every(tests,
    function (check, callback) {
      verify_balance(remote, check.source, check.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

// --> owner: account
// --> seq: sequence number of creating transaction.
// --> taker_gets: json amount
// --> taker_pays: json amount
var verify_offer = function (remote, owner, seq, taker_pays, taker_gets, callback) {
  assert(6 === arguments.length);

  remote.request_ledger_entry('offer')
    .offer_id(owner, seq)
    .on('success', function (m) {
        var wrong = !Amount.from_json(m.node.TakerGets).equals(Amount.from_json(taker_gets), true)
          || !Amount.from_json(m.node.TakerPays).equals(Amount.from_json(taker_pays), true);

        if (wrong)
          console.log("verify_offer: failed: %s", JSON.stringify(m));

        callback(wrong);
      })
    .request();
};

var verify_offer_not_found = function (remote, owner, seq, callback) {
  assert(4 === arguments.length);

  remote.request_ledger_entry('offer')
    .offer_id(owner, seq)
    .on('success', function (m) {
        console.log("verify_offer_not_found: found offer: %s", JSON.stringify(m));

        callback('entryFound');
      })
    .on('error', function (m) {
        // console.log("verify_offer_not_found: success: %s", JSON.stringify(m));

        callback('remoteError' !== m.error
          || 'entryNotFound' !== m.remote.error);
      })
    .request();
};

var verify_owner_count = function (remote, account, value, callback) {
  assert(4 === arguments.length);

  remote.request_owner_count(account, 'CURRENT')
    .once('owner_count', function (owner_count) {
        if (owner_count !== value)
          console.log("owner_count: %s/%d", owner_count, value);

        callback(owner_count !== value);
      })
    .request();
};

var verify_owner_counts = function (remote, counts, callback) {
  var tests = [];

  for (var src in counts) {
      tests.push( { "source" : src, "count" : counts[src] } );
  }

  async.every(tests,
    function (check, callback) {
      verify_owner_count(remote, check.source, check.count,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

exports.account_dump            = account_dump;
exports.build_setup             = build_setup;
exports.build_teardown          = build_teardown;
exports.create_accounts         = create_accounts;
exports.credit_limit            = credit_limit;
exports.credit_limits           = credit_limits;
exports.ledger_close            = ledger_close;
exports.payment                 = payment;
exports.payments                = payments;
exports.transfer_rate           = transfer_rate;
exports.verify_balance          = verify_balance;
exports.verify_balances         = verify_balances;
exports.verify_limit            = verify_limit;
exports.verify_offer            = verify_offer;
exports.verify_offer_not_found  = verify_offer_not_found;
exports.verify_owner_count      = verify_owner_count;
exports.verify_owner_counts     = verify_owner_counts;

// vim:sw=2:sts=2:ts=8:et
<commit_msg>Add trust auto clear. Fixes #28<commit_after>var async   = require("async");

var Amount  = require("../src/js/amount").Amount;
var Remote  = require("../src/js/remote").Remote;
var Server  = require("./server").Server;

var config  = require('../src/js/config').load(require('./config'));

var account_dump = function (remote, account, callback) {
  var self = this;

  async.waterfall([
      function (callback) {
        self.what = "Get latest account_root";

        remote
          .request_ledger_entry('account_root')
          .ledger_hash(remote.ledger_hash())
          .account_root("root")
          .on('success', function (r) {
              //console.log("account_root: %s", JSON.stringify(r, undefined, 2));

              callback();
            })
          .on('error', function(m) {
              console.log("error: %s", m);

              buster.assert(false);
              callback();
            })
          .request();
      },
    ], function (error) {
      callback(error);
    });

  // get closed ledger hash
  // get account root
  // construct a json result
  //
};

/**
 * Helper called by test cases to generate a setUp routine.
 *
 * By default you would call this without options, but it is useful to
 * be able to plug options in during development for quick and easy
 * debugging.
 *
 * @example
 *   buster.testCase("Foobar", {
 *     setUp: testutils.build_setup({verbose: true}),
 *     // ...
 *   });
 *
 * @param opts {Object} These options allow quick-and-dirty test-specific
 *   customizations of your test environment.
 * @param opts.verbose {Bool} Enable all debug output (then cover your ears
 *   and run)
 * @param opts.verbose_ws {Bool} Enable tracing in the Remote class. Prints
 *   websocket traffic.
 * @param opts.verbose_server {Bool} Set the -v option when running rippled.
 * @param opts.no_server {Bool} Don't auto-run rippled.
 * @param host {String} Identifier for the host configuration to be used.
 */
var build_setup = function (opts, host) {
  opts = opts || {};

  // Normalize options
  if (opts.verbose) {
    opts.verbose_ws = true;
    opts.verbose_server = true;
  };

  return function (done) {
    var self = this;

    host = host || config.server_default;

    this.store = this.store || {};

    var data = this.store[host] = this.store[host] || {};

    data.opts = opts;

    async.series([
      function runServerStep(callback) {
        if (opts.no_server) return callback();

        data.server = Server
                        .from_config(host, !!opts.verbose_server)
                        .on('started', callback)
                        .on('exited', function () {
                            // If know the remote, tell it server is gone.
                            if (self.remote)
                              self.remote.server_fatal();
                          })
                        .start();
      },
      function connectWebsocketStep(callback) {
        self.remote = data.remote =
          Remote
            .from_config(host, !!opts.verbose_ws)
            .once('ledger_closed', callback)
            .connect();
      }
    ], done);
  };
};

/**
 * Generate tearDown routine.
 *
 * @param host {String} Identifier for the host configuration to be used.
 */
var build_teardown = function (host) {

  return function (done) {
    host = host || config.server_default;

    var data = this.store[host];
    var opts = data.opts;

    async.series([
      function disconnectWebsocketStep(callback) {

        data.remote
          .on('disconnected', callback)
          .connect(false);
      },
      function stopServerStep(callback) {
        if (opts.no_server)
        {
          return callback();
        }

        data.server
          .on('stopped', callback)
          .stop();
      }
    ], done);
  };
};

var create_accounts = function (remote, src, amount, accounts, callback) {
  assert(5 === arguments.length);

  remote.set_account_seq(src, 1);

  async.forEach(accounts, function (account, callback) {
    // Cache the seq as 1.
    // Otherwise, when other operations attempt to opperate async against the account they may get confused.
    remote.set_account_seq(account, 1);

    remote.transaction()
      .payment(src, account, amount)
      .on('proposed', function (m) {
          // console.log("proposed: %s", JSON.stringify(m));

          callback(m.result != 'tesSUCCESS');
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .submit();
    }, callback);
};

var credit_limit = function (remote, src, amount, callback) {
  assert(4 === arguments.length);

  var _m      = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/);
  if (!_m) {
    console.log("credit_limit: parse error: %s", amount);

    callback('parse_error');
  }
  else
  {
    // console.log("credit_limit: parsed: %s", JSON.stringify(_m, undefined, 2));
    var _account_limit  = _m[1];
    var _quality_in     = _m[2];
    var _quality_out    = _m[3];

    remote.transaction()
      .ripple_line_set(src, _account_limit, _quality_in, _quality_out)
      .on('proposed', function (m) {
          // console.log("proposed: %s", JSON.stringify(m));

          callback(m.result != 'tesSUCCESS');
        })
      .on('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .submit();
  }
};

var verify_limit = function (remote, src, amount, callback) {
  assert(4 === arguments.length);

  var _m      = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/);
  if (!_m) {
    // console.log("credit_limit: parse error: %s", amount);

    callback('parse_error');
  }
  else
  {
    // console.log("verify_limit: parsed: %s", JSON.stringify(_m, undefined, 2));
    var _account_limit  = _m[1];
    var _quality_in     = _m[2];
    var _quality_out    = _m[3];

    var _limit          = Amount.from_json(_account_limit);

    remote.request_ripple_balance(src, _limit.issuer().to_json(), _limit.currency().to_json(), 'CURRENT')
      .once('ripple_state', function (m) {
          buster.assert(m.account_limit.equals(_limit));
          buster.assert('undefined' === _quality_in || m.account_quality_in == _quality_in);
          buster.assert('undefined' === _quality_out || m.account_quality_out == _quality_out);

          callback();
        })
      .once('error', function (m) {
          // console.log("error: %s", JSON.stringify(m));

          callback(m);
        })
      .request();
  }
};

var credit_limits = function (remote, balances, callback) {
  assert(3 === arguments.length);

  var limits = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      limits.push( { "source" : src, "amount" : values[index] } );
    }
  }

  async.every(limits,
    function (limit, callback) {
      credit_limit(remote, limit.source, limit.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

var ledger_close = function (remote, callback) {
  remote.once('ledger_closed', function (m) { callback(); }).ledger_accept();
}

var payment = function (remote, src, dst, amount, callback) {
  assert(5 === arguments.length);

  remote.transaction()
    .payment(src, dst, amount)
    .on('proposed', function (m) {
        // console.log("proposed: %s", JSON.stringify(m));

        callback(m.result != 'tesSUCCESS');
      })
    .on('error', function (m) {
        // console.log("error: %s", JSON.stringify(m));

        callback(m);
      })
    .submit();
};

var payments = function (remote, balances, callback) {
  assert(3 === arguments.length);

  var sends = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      var amount_json = values[index];
      var amount      = Amount.from_json(amount_json);

      sends.push( { "source" : src, "destination" : amount.issuer().to_json(), "amount" : amount_json } );
    }
  }

  async.every(sends,
    function (send, callback) {
      payment(remote, send.source, send.destination, send.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

var transfer_rate = function (remote, src, billionths, callback) {
  assert(4 === arguments.length);

  remote.transaction()
    .account_set(src)
    .transfer_rate(billionths)
    .on('proposed', function (m) {
        // console.log("proposed: %s", JSON.stringify(m));

        callback(m.result != 'tesSUCCESS');
      })
    .on('error', function (m) {
        // console.log("error: %s", JSON.stringify(m));

        callback(m);
      })
    .submit();
};

var verify_balance = function (remote, src, amount_json, callback) {
  assert(4 === arguments.length);
  var amount_req  = Amount.from_json(amount_json);

  if (amount_req.is_native()) {
    remote.request_account_balance(src, 'CURRENT')
      .once('account_balance', function (amount_act) {
          if (!amount_act.equals(amount_req, true)) {
            console.log("verify_balance: failed: %s / %s",
              amount_act.to_text_full(),
              amount_req.to_text_full());
          }

          callback(!amount_act.equals(amount_req, true));
        })
      .request();
  }
  else {
    remote.request_ripple_balance(src, amount_req.issuer().to_json(), amount_req.currency().to_json(), 'CURRENT')
      .once('ripple_state', function (m) {
        // console.log("BALANCE: %s", JSON.stringify(m));
        // console.log("account_balance: %s", m.account_balance.to_text_full());
        // console.log("account_limit: %s", m.account_limit.to_text_full());
        // console.log("issuer_balance: %s", m.issuer_balance.to_text_full());
        // console.log("issuer_limit: %s", m.issuer_limit.to_text_full());

          var account_balance = Amount.from_json(m.account_balance);

          if (!account_balance.equals(amount_req, true)) {
            console.log("verify_balance: failed: %s vs %s / %s: %s",
                        src,
                        account_balance.to_text_full(),
                        amount_req.to_text_full(),
                        account_balance.not_equals_why(amount_req, true));
          }

          callback(!account_balance.equals(amount_req, true));
        })
      .request();
  }
};

var verify_balances = function (remote, balances, callback) {
  var tests = [];

  for (var src in balances) {
    var values_src  = balances[src];
    var values      = 'string' === typeof values_src ? [ values_src ] : values_src;

    for (var index in values) {
      tests.push( { "source" : src, "amount" : values[index] } );
    }
  }

  async.every(tests,
    function (check, callback) {
      verify_balance(remote, check.source, check.amount,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

// --> owner: account
// --> seq: sequence number of creating transaction.
// --> taker_gets: json amount
// --> taker_pays: json amount
var verify_offer = function (remote, owner, seq, taker_pays, taker_gets, callback) {
  assert(6 === arguments.length);

  remote.request_ledger_entry('offer')
    .offer_id(owner, seq)
    .on('success', function (m) {
        var wrong = !Amount.from_json(m.node.TakerGets).equals(Amount.from_json(taker_gets), true)
          || !Amount.from_json(m.node.TakerPays).equals(Amount.from_json(taker_pays), true);

        if (wrong)
          console.log("verify_offer: failed: %s", JSON.stringify(m));

        callback(wrong);
      })
    .request();
};

var verify_offer_not_found = function (remote, owner, seq, callback) {
  assert(4 === arguments.length);

  remote.request_ledger_entry('offer')
    .offer_id(owner, seq)
    .on('success', function (m) {
        console.log("verify_offer_not_found: found offer: %s", JSON.stringify(m));

        callback('entryFound');
      })
    .on('error', function (m) {
        // console.log("verify_offer_not_found: success: %s", JSON.stringify(m));

        callback('remoteError' !== m.error
          || 'entryNotFound' !== m.remote.error);
      })
    .request();
};

var verify_owner_count = function (remote, account, value, callback) {
  assert(4 === arguments.length);

  remote.request_owner_count(account, 'CURRENT')
    .once('owner_count', function (owner_count) {
        if (owner_count !== value)
          console.log("owner_count: %s/%d", owner_count, value);

        callback(owner_count !== value);
      })
    .request();
};

var verify_owner_counts = function (remote, counts, callback) {
  var tests = [];

  for (var src in counts) {
      tests.push( { "source" : src, "count" : counts[src] } );
  }

  async.every(tests,
    function (check, callback) {
      verify_owner_count(remote, check.source, check.count,
        function (mismatch) { callback(!mismatch); });
    },
    function (every) {
      callback(!every);
    });
};

exports.account_dump            = account_dump;
exports.build_setup             = build_setup;
exports.build_teardown          = build_teardown;
exports.create_accounts         = create_accounts;
exports.credit_limit            = credit_limit;
exports.credit_limits           = credit_limits;
exports.ledger_close            = ledger_close;
exports.payment                 = payment;
exports.payments                = payments;
exports.transfer_rate           = transfer_rate;
exports.verify_balance          = verify_balance;
exports.verify_balances         = verify_balances;
exports.verify_limit            = verify_limit;
exports.verify_offer            = verify_offer;
exports.verify_offer_not_found  = verify_offer_not_found;
exports.verify_owner_count      = verify_owner_count;
exports.verify_owner_counts     = verify_owner_counts;

// vim:sw=2:sts=2:ts=8:et
<|endoftext|>