<commit_before>module('Tire core.js', {});

test('isFun', function () {
  ok($.isFun(function () {}), 'Should return true for function');
});

test('isNum', function () {
  ok($.isNum(2), 'Should return true for number');
});

test('isObj', function () {
  ok($.isObj(tire), 'Should return true for object');
});

test('isStr', function () {
  ok($.isStr('tire'), 'Should return true for string');
});

test('isArr', function () {
  ok($.isArr([]), 'Should return true for array');
});

test('parseJSON', function () {
  window.JSON = undefined;
  ok(!!($.parseJSON('{"a":"b"}') instanceof Object || !''), true, 'Should parse JSON string to object or return empty string');
});

module('Selectors', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('ID Selector', function () {
  expect(3);
  elm = $('#test');
  equal(elm.length, 1, 'Should return length 1 for existing elements with specified ID');
  equal(elm[0].innerHTML, 'test text', 'Should contain innerHTML as exists in markup');
  elm = $('#donotexists');
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

test('Class name Selector', function () {
  expect(3);
  elm = $('.test');
  equal(elm.length, 1, 'Should return length 1 for existing elements with specified classname');
  equal(elm[0].innerHTML, 'test text', 'Should contain innerHTML as exists in markup');
  elm = $('.donotexists');
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

test('Tag name Selector', function () {
  expect(2);
  elm = $('ul');
  equal(elm.length, 1, 'Should return length 1 for existing elements with specified tagname');
  elm = $('blink');
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

test('Elements reference selector', function () {
  expect(2);
  elm = $(document.body);
  equal(elm.length, 1, 'Should return length 1 for existing body element');
  elm = $(window);
  equal(elm[0], window, 'Should be able to pass window as selector');
});

test('HTML string selector', function () {
  expect(2);
  elm = $('Hello, world!');
  equal(elm.length, 1, 'Should return length 1 for existing elements');
  ok(elm[0] instanceof HTMLAnchorElement, 'Should be a instance of HTMLAnchorElement');
});

test('Empty selectors', function () {
  expect(3);
  elm = $(undefined);
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
  elm = $(null);
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
  elm = $("");
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

module('Tire dom.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('is', function () {
  ok($('.test').is('div'), true, 'Should return true if the element matches the selector');
});

test('closest', function () {
  equal($('div').closest('body')[0], document.body, 'Should return body element');
});

test('text', function () {
  expect(2);
  $('.test').text('test text');
  equal($('.test').text(), 'test text', 'Should return text content for element');
  $('.trunk').text('test text');
  equal($('.trunk').text(), 'test text', 'Should return text content for element');
});

test('val', function () {
  equal($('input[type=text]').val(), '', 'Should return value of input element');
  $('input[type=text]').val('test text');
  equal($('input[type=text]').val(), 'test text', 'Should return value of input element');
});

test('html', function () {
  expect(2);
  elm = $('.html');
  equal(elm.html(), 'test text', 'Should return inner html for element');
  elm.html('html test');
  equal(elm.html(), 'html test', 'Should return inner html for element after it changed');
});

test('append', function () {
  elm = $('.html');
  elm.append('
append
');
  equal(elm[0].childNodes[1].innerHTML, 'append', 'Should return inner html for element');
});

test('prepend', function () {
  elm = $('.html');
  elm.prepend('
prepend
');
  equal(elm[0].childNodes[0].innerHTML, 'prepend', 'Should return inner html for element');
});

test('before', function () {
  $('.html').before('
before
');
  equal($('.html')[0].previousSibling.innerHTML, 'before', 'Should return inner html for element');
});

test('after', function () {
  $('.html').after('
after
');
  elm = $('.html')[0];
  //
after
 in IE8 is found using only one nextSibling, have to investigate this but this will fix the test for now.
  var result = elm.nextSibling.nextSibling.innerHTML === 'test text' ? elm.nextSibling.innerHTML : elm.nextSibling.nextSibling.innerHTML;
  equal(result, 'after', 'Should return inner html for element');
});

test('remove', function () {
  $('#remove-me-a').remove();
  equal(document.getElementById('remove-me-a'), null, 'Element should not exists after calling remove');
});

test('empty', function () {
  $('.html').empty();
  equal($('.html').html(), '', 'Should return empty inner html after empty');
});

module('Tire css.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('css', function () {
  expect(4);
  var elm = $('.test');
  elm.css('color', 'black');
  var value = elm.css('color');
  ok(value === 'rgb(0, 0, 0)' || value === '#000000' || value === 'black', 'Should return css property from element');
  elm.css({ backgroundColor: 'black', fontSize: 12 });
  ok(!!elm.css('backgroundColor'), true, 'Should return css property from element');
  ok(!!elm.css('fontSize'), true, 'Should return css property from element');
  equal(elm.css('test'), '', 'Should return empty string if css property is not found');
});

test('hide', function () {
  $('.test').hide();
  equal($('.test').css('display'), 'none', 'Should return none for display property when element is hidden');
});

test('show', function () {
  $('.test').show();
  notEqual($('.test').css('display'), 'none', 'Should return block for display property when element is visible');
});

module('Tire attributes.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('addClass', function () {
  $('.test').addClass('dustin');
  equal($('.test').hasClass('dustin'), true, 'Should return true if the element has the class after adding it');
  $('.test').addClass('item-1 item-2 item-3');
});

test('removeClass', function () {
  expect(2);
  $('.test').removeClass('dustin');
  equal($('.test').hasClass('dustin'), false, 'Should return false when the class if being removed');
  $('.test').removeClass('item-2');
  equal($('.test').attr('class'), 'test item-1 item-3', 'Should return classes');
});

test('attr', function () {
  $('.test').attr('rel', 'dustin');
  equal($('.test').attr('rel'), 'dustin', 'Should add attribute to element');
});

test('removeAttr', function () {
  expect(2);
  $('.test').removeAttr('rel');
  equal($('.test').attr('rel'), null, 'Should remove the attrbute from element');
  $('.test').removeAttr('data-tag');
  equal($('.test').attr('data-tag'), null, 'Should remove the attribute from element');
});

module('Tire elements.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('filter', function () {
  expect(2);
  elm = $('div').filter(function () {
    if ($(this).hasClass('test')) return true;
  });
  equal(elm[0], $('.test')[0], 'Filter should only return the elements we filter');
  equal(elm.length, 1, 'Should return length 1 since the are only one .test element');
});

test('not', function () {
  ok($('div').not('.test') !== $('div'), false, 'Should not be equal to div after removing element with not');
});<commit_msg>missed to save test comment<commit_after>module('Tire core.js', {});

test('isFun', function () {
  ok($.isFun(function () {}), 'Should return true for function');
});

test('isNum', function () {
  ok($.isNum(2), 'Should return true for number');
});

test('isObj', function () {
  ok($.isObj(tire), 'Should return true for object');
});

test('isStr', function () {
  ok($.isStr('tire'), 'Should return true for string');
});

test('isArr', function () {
  ok($.isArr([]), 'Should return true for array');
});

test('parseJSON', function () {
  // Not so pretty. Can add support for JSON parse via (new Function('return ' + str))(); But maybe don't need it.
  ok(!!($.parseJSON('{"a":"b"}') instanceof Object || !''), true, 'Should parse JSON string to object or return empty string');
});

module('Selectors', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('ID Selector', function () {
  expect(3);
  elm = $('#test');
  equal(elm.length, 1, 'Should return length 1 for existing elements with specified ID');
  equal(elm[0].innerHTML, 'test text', 'Should contain innerHTML as exists in markup');
  elm = $('#donotexists');
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

test('Class name Selector', function () {
  expect(3);
  elm = $('.test');
  equal(elm.length, 1, 'Should return length 1 for existing elements with specified classname');
  equal(elm[0].innerHTML, 'test text', 'Should contain innerHTML as exists in markup');
  elm = $('.donotexists');
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

test('Tag name Selector', function () {
  expect(2);
  elm = $('ul');
  equal(elm.length, 1, 'Should return length 1 for existing elements with specified tagname');
  elm = $('blink');
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

test('Elements reference selector', function () {
  expect(2);
  elm = $(document.body);
  equal(elm.length, 1, 'Should return length 1 for existing body element');
  elm = $(window);
  equal(elm[0], window, 'Should be able to pass window as selector');
});

test('HTML string selector', function () {
  expect(2);
  elm = $('Hello, world!');
  equal(elm.length, 1, 'Should return length 1 for existing elements');
  ok(elm[0] instanceof HTMLAnchorElement, 'Should be a instance of HTMLAnchorElement');
});

test('Empty selectors', function () {
  expect(3);
  elm = $(undefined);
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
  elm = $(null);
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
  elm = $("");
  equal(elm.length, 0, 'Should return length 0 for non-existing elements');
});

module('Tire dom.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('is', function () {
  ok($('.test').is('div'), true, 'Should return true if the element matches the selector');
});

test('closest', function () {
  equal($('div').closest('body')[0], document.body, 'Should return body element');
});

test('text', function () {
  expect(2);
  $('.test').text('test text');
  equal($('.test').text(), 'test text', 'Should return text content for element');
  $('.trunk').text('test text');
  equal($('.trunk').text(), 'test text', 'Should return text content for element');
});

test('val', function () {
  equal($('input[type=text]').val(), '', 'Should return value of input element');
  $('input[type=text]').val('test text');
  equal($('input[type=text]').val(), 'test text', 'Should return value of input element');
});

test('html', function () {
  expect(2);
  elm = $('.html');
  equal(elm.html(), 'test text', 'Should return inner html for element');
  elm.html('html test');
  equal(elm.html(), 'html test', 'Should return inner html for element after it changed');
});

test('append', function () {
  elm = $('.html');
  elm.append('
append
');
  equal(elm[0].childNodes[1].innerHTML, 'append', 'Should return inner html for element');
});

test('prepend', function () {
  elm = $('.html');
  elm.prepend('
prepend
');
  equal(elm[0].childNodes[0].innerHTML, 'prepend', 'Should return inner html for element');
});

test('before', function () {
  $('.html').before('
before
');
  equal($('.html')[0].previousSibling.innerHTML, 'before', 'Should return inner html for element');
});

test('after', function () {
  $('.html').after('
after
');
  elm = $('.html')[0];
  //
after
 in IE8 is found using only one nextSibling, have to investigate this but this will fix the test for now.
  var result = elm.nextSibling.nextSibling.innerHTML === 'test text' ? elm.nextSibling.innerHTML : elm.nextSibling.nextSibling.innerHTML;
  equal(result, 'after', 'Should return inner html for element');
});

test('remove', function () {
  $('#remove-me-a').remove();
  equal(document.getElementById('remove-me-a'), null, 'Element should not exists after calling remove');
});

test('empty', function () {
  $('.html').empty();
  equal($('.html').html(), '', 'Should return empty inner html after empty');
});

module('Tire css.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('css', function () {
  expect(4);
  var elm = $('.test');
  elm.css('color', 'black');
  var value = elm.css('color');
  ok(value === 'rgb(0, 0, 0)' || value === '#000000' || value === 'black', 'Should return css property from element');
  elm.css({ backgroundColor: 'black', fontSize: 12 });
  ok(!!elm.css('backgroundColor'), true, 'Should return css property from element');
  ok(!!elm.css('fontSize'), true, 'Should return css property from element');
  equal(elm.css('test'), '', 'Should return empty string if css property is not found');
});

test('hide', function () {
  $('.test').hide();
  equal($('.test').css('display'), 'none', 'Should return none for display property when element is hidden');
});

test('show', function () {
  $('.test').show();
  notEqual($('.test').css('display'), 'none', 'Should return block for display property when element is visible');
});

module('Tire attributes.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('addClass', function () {
  $('.test').addClass('dustin');
  equal($('.test').hasClass('dustin'), true, 'Should return true if the element has the class after adding it');
  $('.test').addClass('item-1 item-2 item-3');
});

test('removeClass', function () {
  expect(2);
  $('.test').removeClass('dustin');
  equal($('.test').hasClass('dustin'), false, 'Should return false when the class if being removed');
  $('.test').removeClass('item-2');
  equal($('.test').attr('class'), 'test item-1 item-3', 'Should return classes');
});

test('attr', function () {
  $('.test').attr('rel', 'dustin');
  equal($('.test').attr('rel'), 'dustin', 'Should add attribute to element');
});

test('removeAttr', function () {
  expect(2);
  $('.test').removeAttr('rel');
  equal($('.test').attr('rel'), null, 'Should remove the attrbute from element');
  $('.test').removeAttr('data-tag');
  equal($('.test').attr('data-tag'), null, 'Should remove the attribute from element');
});

module('Tire elements.js', {
  setup: function () {
    var elm;
  },
  teardown: function () {
    elm = null;
  }
});

test('filter', function () {
  expect(2);
  elm = $('div').filter(function () {
    if ($(this).hasClass('test')) return true;
  });
  equal(elm[0], $('.test')[0], 'Filter should only return the elements we filter');
  equal(elm.length, 1, 'Should return length 1 since the are only one .test element');
});

test('not', function () {
  ok($('div').not('.test') !== $('div'), false, 'Should not be equal to div after removing element with not');
});<|endoftext|>


<commit_before>module("Model");

test("defining attributes when instanciating a model", function() {
  var Post = Model("post")
  var post

  post = new Post(undefined)
  deepEqual({}, post.attributes)

  var attributes = { a: "a", b: "b" }
  post = new Post(attributes)
  attributes.a = "b"
  deepEqual("a", post.attributes.a)
})

test("attr, attributes, changes, reset, save, destroy", function() {
  var Post = Model("post");
  var post = new Post({ title: "Foo", body: "..." });

  deepEqual(post.attributes, { title: "Foo", body: "..." });
  deepEqual(post.changes, {});

  var attr = post.attr();
  deepEqual(attr, { title: "Foo", body: "..." });
  attr.title = "Bar";
  equal(post.attributes.title, "Foo", "`attr` should return a copy of attributes not the real thing");

  post.attr("title", null);
  equal(post.attributes.title, "Foo", "attributes should be unchanged");
  equal(post.changes.title, null);
  equal(post.attr("title"), null, "null value should be read back as null");

  post.attr("title", "Foo");
  equal(post.attributes.title, "Foo");
  ok(!("title" in post.changes), "unchanged value shouldn't appear in changes");
  equal(post.attr("title"), "Foo");

  post.reset();
  deepEqual(post.attributes, { title: "Foo", body: "..." });
  deepEqual(post.changes, {});
  deepEqual(post.attr(), { title: "Foo", body: "..." });

  // Set attribute using attr.
  ok(post.attr("title", "Bar") === post, "returns self");

  // Check attributes and changes.
  equal(post.attr("title"), "Bar");
  deepEqual(post.attributes, { title: "Foo", body: "..." }, "attributes should be unchanged");
  deepEqual(post.changes, { title: "Bar" });
  deepEqual(post.attr(), { title: "Bar", body: "..." });

  ok(post.reset() === post, "returns self");

  equal(post.attr("title"), "Foo");
  deepEqual(post.changes, {});

  // Set again
  post.attr("title", "Bar");

  deepEqual(post.attributes, { title: "Foo", body: "..." });
  deepEqual(post.changes, { title: "Bar" });

  ok(post.save() === post);

  deepEqual(post.attributes, { title: "Bar", body: "..." });
  deepEqual(post.changes, {});

  ok(post.attr({ title: "Foo", bar: "Bar" }) === post, "returns self");

  deepEqual(post.attributes, { title: "Bar", body: "..." });
  deepEqual(post.changes, { title: "Foo", bar: "Bar" });

  ok(post.save(function(success) {
    ok(success);
  }) === post);

  deepEqual(post.attributes, { bar: "Bar", body: "...", title: "Foo" });
  deepEqual(post.changes, {});

  post.destroy(function(success) {
    ok(success);
  });
});

test("custom methods", function() {
  var Post = Model("post", function(klass, proto) {
    this.foo = function() { return "foo" }
    klass.bar = function() { return "bar" }
    proto.foo = function() { return "foo" }
    this.prototype.bar = function() { return "bar" }
  })

  equal(Post.foo(), "foo");
  equal(Post.bar(), "bar");

  var post = new Post();

  equal(post.foo(), "foo");
  equal(post.bar(), "bar");
});

test("valid, validate, errors", function() {
  var Post = Model("post", function() {
    this.prototype.validate = function() {
      if (!/\S/.test(this.attr("body") || ""))
        this.errors.add("body", "can't be blank");

      if (this.attr("title") == "Foo")
        this.errors.add("title", "should not be Foo");
      if (this.attr("title") != "Bar")
        this.errors.add("title", "should be Bar");
    }
  });

  var post = new Post();

  ok(!post.valid());
  equal(post.errors.size(), 2);
  deepEqual(post.errors.on("body"), ["can't be blank"]);
  deepEqual(post.errors.on("title"), ["should be Bar"]);

  post.save(function(success) {
    ok(!success);
  });

  post.attr("title", "Foo");

  ok(!post.valid());
  equal(post.errors.size(), 3);
  deepEqual(post.errors.on("body"), ["can't be blank"]);
  deepEqual(post.errors.on("title"), ["should not be Foo", "should be Bar"]);

  post.reset();

  equal(post.errors.size(), 0);
  deepEqual(post.errors.on("body"), []);
  deepEqual(post.errors.on("title"), []);

  post.attr({
    body: "...",
    title: "Bar"
  });

  ok(post.valid());
  equal(post.errors.size(), 0);
  deepEqual(post.errors.on("body"), []);
  deepEqual(post.errors.on("title"), []);

  post.save(function(success) {
    ok(success);
  });

  deepEqual(post.changes, {});
});

test('model collection "class" methods', function() {
  var Post = Model("post");

  ok(Post.first() === undefined, "collection starts empty");

  var post = new Post();
  ok(Post.first() === undefined, "collection is unaffected");

  post.save();
  ok(Post.first() === post, "post added to collection automatically");

  post.destroy();
  ok(Post.first() === undefined, "post removed from collection automatically");
});

test("persistence", function() {
  var results = [];
  var post;

  var TestPersistence = function() {
    return {
      create: function(model, callback) {
        ok(model === post)
        results.push("create");
        results.push(callback());
      },

      destroy: function(model, callback) {
        ok(model === post)
        results.push("destroy");
        results.push(callback());
      },

      update: function(model, callback) {
        ok(model === post)
        results.push("update");
        results.push(callback());
      }
    }
  };

  var Post = Model("post", function() {
    this.persistence(TestPersistence)
  });

  var callback = function() {
    return "callback";
  };

  post = new Post();
  post.save(callback);
  post.attributes.id = 1;
  post.save(callback);
  post.destroy(callback);

  deepEqual(results, [
    "create", "callback",
    "update", "callback",
    "destroy", "callback"
  ]);
});

test("persistence failure", function() {
  var TestPersistence = function() {
    return {
      create: function(model, callback) {
        callback(false);
      },

      destroy: function(model, callback) {
        callback(false);
      },

      update: function(model, callback) {
        callback(false);
      }
    }
  };

  var Post = Model("post", function() {
    this.persistence(TestPersistence)
  });

  var events = [];

  // Stub trigger and capture its argument.
  Post.prototype.trigger = function(name) {
    events.push(name);
  };

  post = new Post();
  post.save();

  deepEqual(events, [], "should not trigger create event if persistence failed");
  deepEqual(Post.all(), [], "post should not be added to collection");

  post.attributes.id = 1;
  post.save();

  deepEqual(events, [], "should not trigger update event if persistence failed");

  post.destroy();

  deepEqual(events, [], "should not trigger destroy event if persistence failed");
});

test("#initialize", function() {
  var Post = Model("post", function() {
    this.prototype.initialize = function() {
      this.initialized = true
    }
  })

  var post = new Post()

  ok(post.initialized)
})

test("saving a model with an id should add it to the collection if it isn't already present", function() {
  var Post = Model("post")
  var post = new Post({ id: 1 }).save()

  ok(Post.first() === post)
})
<commit_msg>Missing var.<commit_after>module("Model");

test("defining attributes when instanciating a model", function() {
  var Post = Model("post")
  var post

  post = new Post(undefined)
  deepEqual({}, post.attributes)

  var attributes = { a: "a", b: "b" }
  post = new Post(attributes)
  attributes.a = "b"
  deepEqual("a", post.attributes.a)
})

test("attr, attributes, changes, reset, save, destroy", function() {
  var Post = Model("post");
  var post = new Post({ title: "Foo", body: "..." });

  deepEqual(post.attributes, { title: "Foo", body: "..." });
  deepEqual(post.changes, {});

  var attr = post.attr();
  deepEqual(attr, { title: "Foo", body: "..." });
  attr.title = "Bar";
  equal(post.attributes.title, "Foo", "`attr` should return a copy of attributes not the real thing");

  post.attr("title", null);
  equal(post.attributes.title, "Foo", "attributes should be unchanged");
  equal(post.changes.title, null);
  equal(post.attr("title"), null, "null value should be read back as null");

  post.attr("title", "Foo");
  equal(post.attributes.title, "Foo");
  ok(!("title" in post.changes), "unchanged value shouldn't appear in changes");
  equal(post.attr("title"), "Foo");

  post.reset();
  deepEqual(post.attributes, { title: "Foo", body: "..." });
  deepEqual(post.changes, {});
  deepEqual(post.attr(), { title: "Foo", body: "..." });

  // Set attribute using attr.
  ok(post.attr("title", "Bar") === post, "returns self");

  // Check attributes and changes.
  equal(post.attr("title"), "Bar");
  deepEqual(post.attributes, { title: "Foo", body: "..." }, "attributes should be unchanged");
  deepEqual(post.changes, { title: "Bar" });
  deepEqual(post.attr(), { title: "Bar", body: "..." });

  ok(post.reset() === post, "returns self");

  equal(post.attr("title"), "Foo");
  deepEqual(post.changes, {});

  // Set again
  post.attr("title", "Bar");

  deepEqual(post.attributes, { title: "Foo", body: "..." });
  deepEqual(post.changes, { title: "Bar" });

  ok(post.save() === post);

  deepEqual(post.attributes, { title: "Bar", body: "..." });
  deepEqual(post.changes, {});

  ok(post.attr({ title: "Foo", bar: "Bar" }) === post, "returns self");

  deepEqual(post.attributes, { title: "Bar", body: "..." });
  deepEqual(post.changes, { title: "Foo", bar: "Bar" });

  ok(post.save(function(success) {
    ok(success);
  }) === post);

  deepEqual(post.attributes, { bar: "Bar", body: "...", title: "Foo" });
  deepEqual(post.changes, {});

  post.destroy(function(success) {
    ok(success);
  });
});

test("custom methods", function() {
  var Post = Model("post", function(klass, proto) {
    this.foo = function() { return "foo" }
    klass.bar = function() { return "bar" }
    proto.foo = function() { return "foo" }
    this.prototype.bar = function() { return "bar" }
  })

  equal(Post.foo(), "foo");
  equal(Post.bar(), "bar");

  var post = new Post();

  equal(post.foo(), "foo");
  equal(post.bar(), "bar");
});

test("valid, validate, errors", function() {
  var Post = Model("post", function() {
    this.prototype.validate = function() {
      if (!/\S/.test(this.attr("body") || ""))
        this.errors.add("body", "can't be blank");

      if (this.attr("title") == "Foo")
        this.errors.add("title", "should not be Foo");
      if (this.attr("title") != "Bar")
        this.errors.add("title", "should be Bar");
    }
  });

  var post = new Post();

  ok(!post.valid());
  equal(post.errors.size(), 2);
  deepEqual(post.errors.on("body"), ["can't be blank"]);
  deepEqual(post.errors.on("title"), ["should be Bar"]);

  post.save(function(success) {
    ok(!success);
  });

  post.attr("title", "Foo");

  ok(!post.valid());
  equal(post.errors.size(), 3);
  deepEqual(post.errors.on("body"), ["can't be blank"]);
  deepEqual(post.errors.on("title"), ["should not be Foo", "should be Bar"]);

  post.reset();

  equal(post.errors.size(), 0);
  deepEqual(post.errors.on("body"), []);
  deepEqual(post.errors.on("title"), []);

  post.attr({
    body: "...",
    title: "Bar"
  });

  ok(post.valid());
  equal(post.errors.size(), 0);
  deepEqual(post.errors.on("body"), []);
  deepEqual(post.errors.on("title"), []);

  post.save(function(success) {
    ok(success);
  });

  deepEqual(post.changes, {});
});

test('model collection "class" methods', function() {
  var Post = Model("post");

  ok(Post.first() === undefined, "collection starts empty");

  var post = new Post();
  ok(Post.first() === undefined, "collection is unaffected");

  post.save();
  ok(Post.first() === post, "post added to collection automatically");

  post.destroy();
  ok(Post.first() === undefined, "post removed from collection automatically");
});

test("persistence", function() {
  var results = [];
  var post;

  var TestPersistence = function() {
    return {
      create: function(model, callback) {
        ok(model === post)
        results.push("create");
        results.push(callback());
      },

      destroy: function(model, callback) {
        ok(model === post)
        results.push("destroy");
        results.push(callback());
      },

      update: function(model, callback) {
        ok(model === post)
        results.push("update");
        results.push(callback());
      }
    }
  };

  var Post = Model("post", function() {
    this.persistence(TestPersistence)
  });

  var callback = function() {
    return "callback";
  };

  post = new Post();
  post.save(callback);
  post.attributes.id = 1;
  post.save(callback);
  post.destroy(callback);

  deepEqual(results, [
    "create", "callback",
    "update", "callback",
    "destroy", "callback"
  ]);
});

test("persistence failure", function() {
  var TestPersistence = function() {
    return {
      create: function(model, callback) {
        callback(false);
      },

      destroy: function(model, callback) {
        callback(false);
      },

      update: function(model, callback) {
        callback(false);
      }
    }
  };

  var Post = Model("post", function() {
    this.persistence(TestPersistence)
  });

  var events = [];

  // Stub trigger and capture its argument.
  Post.prototype.trigger = function(name) {
    events.push(name);
  };

  var post = new Post();
  post.save();

  deepEqual(events, [], "should not trigger create event if persistence failed");
  deepEqual(Post.all(), [], "post should not be added to collection");

  post.attributes.id = 1;
  post.save();

  deepEqual(events, [], "should not trigger update event if persistence failed");

  post.destroy();

  deepEqual(events, [], "should not trigger destroy event if persistence failed");
});

test("#initialize", function() {
  var Post = Model("post", function() {
    this.prototype.initialize = function() {
      this.initialized = true
    }
  })

  var post = new Post()

  ok(post.initialized)
})

test("saving a model with an id should add it to the collection if it isn't already present", function() {
  var Post = Model("post")
  var post = new Post({ id: 1 }).save()

  ok(Post.first() === post)
})
<|endoftext|>


<commit_before>/**
 * Doodle type-checking functions.
 * @name doodle.utils.types
 * @class
 * @augments Object
 * @static
 */
doodle.utils.types = Object.create({}, (function () {

  /**
   * @name throw_type_error
   * @param {string} type Name of type.
   * @param {string=} caller Name of calling function.
   * @param {string=} params Parameter names for function.
   * @throws {TypeError}
   * @static
   * @private
   */
  function throw_type_error (type, caller, params) {
    if (typeof type !== 'string') {
      throw new TypeError("throw_type_error: type must be a string.");
    }
    caller = (caller === undefined) ? "throw_type_error" : caller;
    params = (params === undefined || params === null) ? "" : '('+params+')';
    throw new TypeError(caller + params +": Parameter must be a "+ type +".");
  }

  return {
    /**
     * Type-checking for a number. Throws a TypeError if the test fails.
     * @name check_number_type
     * @param {Object} obj Object to test.
     * @param {string} caller Function name to print in error message.
     * @param {string} param Parameters to print in error message.
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_number_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (obj, caller, params) {
        return (typeof obj === 'number') ?
          true : throw_type_error('number', caller || 'check_number_type', params);
      }
    },

    /**
     * @name check_boolean_type
     * @param {boolean} bool
     * @param {string} caller Function name to print in error message.
     * @param {string} param Parameters to print in error message.
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_boolean_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (bool, caller, params) {
        return (typeof bool === 'boolean') ?
          true : throw_type_error('boolean', caller || 'check_boolean_type', params);
      }
    },

    /**
     * @name check_string_type
     * @param {string}
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_string_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (str, caller, params) {
        return (typeof str === 'string') ?
          true : throw_type_error('string', caller || 'check_string_type', params);
      }
    },

    /**
     * @name check_function_type
     * @param {Function} fn
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_function_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (fn, caller, params) {
        return (typeof fn === 'function') ?
          true : throw_type_error('function', caller || 'check_function_type', params);
      }
    },

    /**
     * @name check_array_type
     * @param {Array} array
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_array_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (array, caller, params) {
        return (Array.isArray(array)) ?
          true : throw_type_error('array', caller || 'check_array_type', params);
      }
    },

    /**
     * @name check_canvas_type
     * @param {HTMLCanvasElement} canvas
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_canvas_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (canvas, caller, params) {
        return (canvas && typeof canvas.toString === 'function' &&
                canvas.toString() === '[object HTMLCanvasElement]') ?
          true : throw_type_error('canvas element', caller || 'check_canvas_type', params);
      }
    },

    /**
     * @name check_context_type
     * @param {CanvasRenderingContext2D} ctx
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_context_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (ctx, caller, params) {
        return (ctx && typeof ctx.toString === 'function' &&
                ctx.toString() === '[object CanvasRenderingContext2D]') ?
          true : throw_type_error('canvas context', caller || 'check_context_type', params);
      }
    },

    /**
     * @name check_block_element
     * @param {HTMLElement} element
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_block_element': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (element, caller, params) {
        try {
          return (doodle.utils.get_style_property(element, 'display') === 'block') ?
            true : throw_type_error('HTML block element', caller || 'check_block_type', params);
        } catch (e) {
          throw_type_error('HTML block element', caller || 'check_block_type', params);
        }
      }
    }

  };
}()));
<commit_msg>Added an initial version of doodle.utils.type_check, which is a more streamlined function than all the other typechecing ones.<commit_after>/**
 * Doodle type-checking functions.
 * @name doodle.utils.types
 * @class
 * @augments Object
 * @static
 */
doodle.utils.types = Object.create({}, (function () {

  function assert_type (test, arg, type) {
    if (test === false) {
      var err = new TypeError(arg + " must be of type '" + type + "'");
      err.arg = arg;
      err.type = type;
      throw err;
    } else {
      return true;
    }
  }

  function check_arg_type (arg, type) {
    if (typeof type !== 'string') {
      throw new SyntaxError("check_arg_type(arg, type): Invalid type.");
    }
    switch (type) {
    case 'boolean':
      assert_type(typeof arg === 'boolean', arg, type);
      break;
    case 'number':
      assert_type(typeof arg === 'number', arg, type);
      break;
    case 'string':
      assert_type(typeof arg === 'string', arg, type);
      break;
    case 'function':
      assert_type(typeof arg === 'function', arg, type);
      break;
    case 'object':
      assert_type(typeof arg === 'object', arg, type);
      break;
    case 'array':
      assert_type(Array.isArray(arg), arg, type);
      break;
    case 'canvas':
      assert_type(Object.prototype.toLocaleString.call(arg) === '[object HTMLCanvasElement]', arg, type);
      break;
    case 'context':
      assert_type(Object.prototype.toLocaleString.call(arg) === '[object CanvasRenderingContext2D]', arg, type);
      break;
    case 'block':
      assert_type(doodle.utils.get_style_property(element, 'display') === 'block', arg, type);
      break;
    case 'Display':
      assert_type(Object.prototype.toLocaleString.call(arg) === '[object Display]', arg, type);
      break;
    case 'Event':
      assert_type(Object.prototype.toLocaleString.call(arg) === '[object Event]', arg, type);
      break;
    case 'MouseEvent':
      assert_type(Object.prototype.toLocaleString.call(arg) === '[object MouseEvent]', arg, type);
      break;
    default:
      throw new SyntaxError("check_arg_type(arg, type): Unknown type.");
    }
  }

  /**
   * @name check_type
   * @param {*} Argument to type check.
   * @param {type} arg type Name of type.
   * @param {function=} callback Function to run if a TypeError is thrown.
   * @throws {SyntaxError}
   * @throws {TypeError}
   */
  doodle.utils.type_check = function (arg, type, /*[arg, type, ...]*/ callback) {
    var args = Array.prototype.slice.call(arguments),
        len = args.length,
        i = 0;
    if (typeof args[len-1] === 'function') {
      callback = args.pop();
      len = args.length;
    }
    if (len % 2 !== 0) {
      throw new SyntaxError("type_check: Each argument must have an associated type.");
    }
    try {
      for (; i < len; i++) {
        if (i % 2 === 0) {
          check_arg_type(args[i], args[i+1]);
        }
      }
    } catch (err) {
      if (err instanceof TypeError) {
        if (typeof callback === 'function') {
          callback(err.arg, err.type);
        } else {
          console.trace();
          throw err;
        }
      } else {
        throw err;
      }
    }
  }

  /**
   * @name throw_type_error
   * @param {string} type Name of type.
   * @param {string=} caller Name of calling function.
   * @param {string=} params Parameter names for function.
   * @throws {TypeError}
   * @static
   * @private
   */
  function throw_type_error (type, caller, params) {
    if (typeof type !== 'string') {
      throw new TypeError("throw_type_error: type must be a string.");
    }
    caller = (caller === undefined) ? "throw_type_error" : caller;
    params = (params === undefined || params === null) ? "" : '('+params+')';
    throw new TypeError(caller + params +": Parameter must be a "+ type +".");
  }

  return {
    /**
     * Type-checking for a number. Throws a TypeError if the test fails.
     * @name check_number_type
     * @param {Object} obj Object to test.
     * @param {string} caller Function name to print in error message.
     * @param {string} param Parameters to print in error message.
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_number_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (obj, caller, params) {
        return (typeof obj === 'number') ?
          true : throw_type_error('number', caller || 'check_number_type', params);
      }
    },

    /**
     * @name check_boolean_type
     * @param {boolean} bool
     * @param {string} caller Function name to print in error message.
     * @param {string} param Parameters to print in error message.
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_boolean_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (bool, caller, params) {
        return (typeof bool === 'boolean') ?
          true : throw_type_error('boolean', caller || 'check_boolean_type', params);
      }
    },

    /**
     * @name check_string_type
     * @param {string}
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_string_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (str, caller, params) {
        return (typeof str === 'string') ?
          true : throw_type_error('string', caller || 'check_string_type', params);
      }
    },

    /**
     * @name check_function_type
     * @param {Function} fn
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_function_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (fn, caller, params) {
        return (typeof fn === 'function') ?
          true : throw_type_error('function', caller || 'check_function_type', params);
      }
    },

    /**
     * @name check_array_type
     * @param {Array} array
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_array_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (array, caller, params) {
        return (Array.isArray(array)) ?
          true : throw_type_error('array', caller || 'check_array_type', params);
      }
    },

    /**
     * @name check_canvas_type
     * @param {HTMLCanvasElement} canvas
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_canvas_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (canvas, caller, params) {
        return (canvas && typeof canvas.toString === 'function' &&
                canvas.toString() === '[object HTMLCanvasElement]') ?
          true : throw_type_error('canvas element', caller || 'check_canvas_type', params);
      }
    },

    /**
     * @name check_context_type
     * @param {CanvasRenderingContext2D} ctx
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_context_type': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (ctx, caller, params) {
        return (ctx && typeof ctx.toString === 'function' &&
                ctx.toString() === '[object CanvasRenderingContext2D]') ?
          true : throw_type_error('canvas context', caller || 'check_context_type', params);
      }
    },

    /**
     * @name check_block_element
     * @param {HTMLElement} element
     * @param {string} caller
     * @param {string} params
     * @return {boolean}
     * @throws {TypeError}
     * @static
     */
    'check_block_element': {
      enumerable: true,
      writable: false,
      configurable: false,
      value: function (element, caller, params) {
        try {
          return (doodle.utils.get_style_property(element, 'display') === 'block') ?
            true : throw_type_error('HTML block element', caller || 'check_block_type', params);
        } catch (e) {
          throw_type_error('HTML block element', caller || 'check_block_type', params);
        }
      }
    }

  };
}()));
<|endoftext|>


<commit_before>import net from 'net';
import url from 'url';
import crypto from 'crypto';
import ip from 'ip';

export const ATYP_V4 = 1;
export const ATYP_DOMAIN = 3;
export const ATYP_V6 = 4;

export const BYTE_ORDER_BE = 0;
export const BYTE_ORDER_LE = 1;

export class Utils {

  /**
   * convert a number to a buffer with specified length in big-endian
   * @param num
   * @param len
   * @param byteOrder
   * @returns {Buffer}
   */
  static numberToUInt(num, len = 2, byteOrder = BYTE_ORDER_BE) {
    if (len < 1) {
      throw Error('len must be greater than 0');
    }

    const isOutOfRange = num > parseInt(`0x${'ff'.repeat(len)}`);
    if (isOutOfRange) {
      throw Error(`Number ${num} is too long to store in a '${len}' length buffer`);
    }

    const buf = Buffer.alloc(len);
    if (byteOrder === BYTE_ORDER_BE) {
      buf.writeUIntBE(num, 0, len);
    } else {
      buf.writeUIntLE(num, 0, len);
    }
    return buf;
  }

  /**
   * convert an uri to Address
   * @param uri
   * @returns {{type: Number, host: Buffer, port: Buffer}}
   */
  static parseURI(uri) {
    let _uri = uri;
    if (_uri.indexOf('http') !== 0 && _uri.indexOf('https') !== 0) {
      if (_uri.indexOf(':443') !== -1) {
        // e.g, bing.com:443
        _uri = `https://${_uri}`;
      } else {
        // e.g, bing.com
        _uri = `http://${_uri}`;
      }
    }
    const {protocol, hostname} = url.parse(_uri);
    const addrType = net.isIP(hostname) ? (net.isIPv4(hostname) ? ATYP_V4 : ATYP_V6) : ATYP_DOMAIN;
    const port = {'http:': 80, 'https:': 443}[protocol];
    return {
      type: addrType,
      host: net.isIP(hostname) ? ip.toBuffer(hostname) : Buffer.from(hostname),
      port: this.numberToUInt(port)
    };
  }

  /**
   * returns a random integer in [min, max].
   * @param min
   * @param max
   * @returns {Number}
   */
  static getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.ceil(max);
    return Math.floor(crypto.randomBytes(1)[0] / 0xff * (max - min + 1)) + min;
  }

  /**
   * split buffer into chunks, each chunk size is picked randomly from [min, max]
   * @param buffer
   * @param min
   * @param max
   * @returns {Array<Buffer>}
   */
  static getRandomChunks(buffer, min, max) {
    const totalLen = buffer.length;
    const bufs = [];
    let ptr = 0;
    while (ptr < totalLen - 1) {
      const offset = this.getRandomInt(min, max);
      bufs.push(buffer.slice(ptr, ptr + offset));
      ptr += offset;
    }
    if (ptr < totalLen) {
      bufs.push(buffer.slice(ptr));
    }
    return bufs;
  }

  /**
   * split buffer into chunks, the max chunk size is maxSize
   * @param buffer
   * @param maxSize
   * @returns {Array<Buffer>}
   */
  static getChunks(buffer, maxSize) {
    const totalLen = buffer.length;
    const bufs = [];
    let ptr = 0;
    while (ptr < totalLen - 1) {
      bufs.push(buffer.slice(ptr, ptr + maxSize));
      ptr += maxSize;
    }
    if (ptr < totalLen) {
      bufs.push(buffer.slice(ptr));
    }
    return bufs;
  }

  /**
   * return UTC timestamp as buffer
   * @returns {Buffer}
   */
  static getUTC() {
    const ts = Math.floor((new Date()).getTime() / 1e3);
    return this.numberToUInt(ts, 4, BYTE_ORDER_BE);
  }

  /**
   * convert string to buffer
   * @param str
   * @returns {Buffer}
   */
  static stringToBuffer(str) {
    return Buffer.from(str, 'hex');
  }

  /**
   * verify hostname
   *
   * @param hostname
   * @returns {boolean}
   *
   * @reference
   *   http://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php
   */
  static isValidHostname(hostname) {
    // overall length check
    if (hostname.length < 1 || hostname.length > 253) {
      return false;
    }
    // valid chars check
    if (/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname) === false) {
      return false;
    }
    // length of each label
    if (/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname) === false) {
      return false;
    }
    return true;
  }

  /**
   * whether a port is valid or not
   * @param port
   * @returns {boolean}
   */
  static isValidPort(port) {
    if (typeof port !== 'number') {
      return false;
    }
    if (port < 0 || port > 65535) {
      return false;
    }
    return true;
  }

  /**
   * md5 message digest
   * @param buffer
   * @returns {*}
   */
  static md5(buffer) {
    const md5 = crypto.createHash('md5');
    md5.update(buffer);
    return md5.digest();
  }

  /**
   * calculate the HMAC from key and message
   * @param algorithm
   * @param key
   * @param buffer
   * @returns {Buffer}
   */
  static hmac(algorithm, key, buffer) {
    const hmac = crypto.createHmac(algorithm, key);
    return hmac.update(buffer).digest();
  }

  /**
   * EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt
   *
   * @algorithm
   *   D_i = HASH^count(D_(i-1) || data || salt)
   */
  static EVP_BytesToKey(password, keyLen, ivLen) {
    let _data = Buffer.from(password);
    let i = 0;
    const bufs = [];
    while (Buffer.concat(bufs).length < (keyLen + ivLen)) {
      if (i > 0) {
        _data = Buffer.concat([bufs[i - 1], Buffer.from(password)]);
      }
      bufs.push(this.md5(_data));
      i += 1;
    }
    return Buffer.concat(bufs).slice(0, keyLen);
  }

  /**
   * HMAC-based Extract-and-Expand Key Derivation Function
   * @param hash, the message digest algorithm
   * @param salt, a non-secret random value
   * @param ikm, input keying material
   * @param info, optional context and application specific information
   * @param length, length of output keying material in octets
   * @returns {Buffer}
   */
  static HKDF(hash, salt, ikm, info, length) {
    // Step 1: "extract" to fixed length pseudo-random key(prk)
    const prk = this.hmac(hash, salt, ikm);
    // Step 2: "expand" prk to several pseudo-random keys(okm)
    let t = Buffer.alloc(0);
    let okm = Buffer.alloc(0);
    for (let i = 0; i < Math.ceil(length / prk.length); ++i) {
      t = this.hmac(hash, prk, Buffer.concat([t, info, Buffer.alloc(1, i + 1)]));
      okm = Buffer.concat([okm, t]);
    }
    // Step 3: crop okm to desired length
    return okm.slice(0, length);
  }

}
<commit_msg>chore(utils): rename stringToBuffer -> hexStringToBuffer<commit_after>import net from 'net';
import url from 'url';
import crypto from 'crypto';
import ip from 'ip';

export const ATYP_V4 = 1;
export const ATYP_DOMAIN = 3;
export const ATYP_V6 = 4;

export const BYTE_ORDER_BE = 0;
export const BYTE_ORDER_LE = 1;

export class Utils {

  /**
   * convert a number to a buffer with specified length in big-endian
   * @param num
   * @param len
   * @param byteOrder
   * @returns {Buffer}
   */
  static numberToUInt(num, len = 2, byteOrder = BYTE_ORDER_BE) {
    if (len < 1) {
      throw Error('len must be greater than 0');
    }

    const isOutOfRange = num > parseInt(`0x${'ff'.repeat(len)}`);
    if (isOutOfRange) {
      throw Error(`Number ${num} is too long to store in a '${len}' length buffer`);
    }

    const buf = Buffer.alloc(len);
    if (byteOrder === BYTE_ORDER_BE) {
      buf.writeUIntBE(num, 0, len);
    } else {
      buf.writeUIntLE(num, 0, len);
    }
    return buf;
  }

  /**
   * convert an uri to Address
   * @param uri
   * @returns {{type: Number, host: Buffer, port: Buffer}}
   */
  static parseURI(uri) {
    let _uri = uri;
    if (_uri.indexOf('http') !== 0 && _uri.indexOf('https') !== 0) {
      if (_uri.indexOf(':443') !== -1) {
        // e.g, bing.com:443
        _uri = `https://${_uri}`;
      } else {
        // e.g, bing.com
        _uri = `http://${_uri}`;
      }
    }
    const {protocol, hostname} = url.parse(_uri);
    const addrType = net.isIP(hostname) ? (net.isIPv4(hostname) ? ATYP_V4 : ATYP_V6) : ATYP_DOMAIN;
    const port = {'http:': 80, 'https:': 443}[protocol];
    return {
      type: addrType,
      host: net.isIP(hostname) ? ip.toBuffer(hostname) : Buffer.from(hostname),
      port: this.numberToUInt(port)
    };
  }

  /**
   * returns a random integer in [min, max].
   * @param min
   * @param max
   * @returns {Number}
   */
  static getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.ceil(max);
    return Math.floor(crypto.randomBytes(1)[0] / 0xff * (max - min + 1)) + min;
  }

  /**
   * split buffer into chunks, each chunk size is picked randomly from [min, max]
   * @param buffer
   * @param min
   * @param max
   * @returns {Array<Buffer>}
   */
  static getRandomChunks(buffer, min, max) {
    const totalLen = buffer.length;
    const bufs = [];
    let ptr = 0;
    while (ptr < totalLen - 1) {
      const offset = this.getRandomInt(min, max);
      bufs.push(buffer.slice(ptr, ptr + offset));
      ptr += offset;
    }
    if (ptr < totalLen) {
      bufs.push(buffer.slice(ptr));
    }
    return bufs;
  }

  /**
   * split buffer into chunks, the max chunk size is maxSize
   * @param buffer
   * @param maxSize
   * @returns {Array<Buffer>}
   */
  static getChunks(buffer, maxSize) {
    const totalLen = buffer.length;
    const bufs = [];
    let ptr = 0;
    while (ptr < totalLen - 1) {
      bufs.push(buffer.slice(ptr, ptr + maxSize));
      ptr += maxSize;
    }
    if (ptr < totalLen) {
      bufs.push(buffer.slice(ptr));
    }
    return bufs;
  }

  /**
   * return UTC timestamp as buffer
   * @returns {Buffer}
   */
  static getUTC() {
    const ts = Math.floor((new Date()).getTime() / 1e3);
    return this.numberToUInt(ts, 4, BYTE_ORDER_BE);
  }

  /**
   * convert string to buffer
   * @param str
   * @returns {Buffer}
   */
  static hexStringToBuffer(str) {
    return Buffer.from(str, 'hex');
  }

  /**
   * verify hostname
   *
   * @param hostname
   * @returns {boolean}
   *
   * @reference
   *   http://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php
   */
  static isValidHostname(hostname) {
    // overall length check
    if (hostname.length < 1 || hostname.length > 253) {
      return false;
    }
    // valid chars check
    if (/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname) === false) {
      return false;
    }
    // length of each label
    if (/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname) === false) {
      return false;
    }
    return true;
  }

  /**
   * whether a port is valid or not
   * @param port
   * @returns {boolean}
   */
  static isValidPort(port) {
    if (typeof port !== 'number') {
      return false;
    }
    if (port < 0 || port > 65535) {
      return false;
    }
    return true;
  }

  /**
   * md5 message digest
   * @param buffer
   * @returns {*}
   */
  static md5(buffer) {
    const md5 = crypto.createHash('md5');
    md5.update(buffer);
    return md5.digest();
  }

  /**
   * calculate the HMAC from key and message
   * @param algorithm
   * @param key
   * @param buffer
   * @returns {Buffer}
   */
  static hmac(algorithm, key, buffer) {
    const hmac = crypto.createHmac(algorithm, key);
    return hmac.update(buffer).digest();
  }

  /**
   * EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt
   *
   * @algorithm
   *   D_i = HASH^count(D_(i-1) || data || salt)
   */
  static EVP_BytesToKey(password, keyLen, ivLen) {
    let _data = Buffer.from(password);
    let i = 0;
    const bufs = [];
    while (Buffer.concat(bufs).length < (keyLen + ivLen)) {
      if (i > 0) {
        _data = Buffer.concat([bufs[i - 1], Buffer.from(password)]);
      }
      bufs.push(this.md5(_data));
      i += 1;
    }
    return Buffer.concat(bufs).slice(0, keyLen);
  }

  /**
   * HMAC-based Extract-and-Expand Key Derivation Function
   * @param hash, the message digest algorithm
   * @param salt, a non-secret random value
   * @param ikm, input keying material
   * @param info, optional context and application specific information
   * @param length, length of output keying material in octets
   * @returns {Buffer}
   */
  static HKDF(hash, salt, ikm, info, length) {
    // Step 1: "extract" to fixed length pseudo-random key(prk)
    const prk = this.hmac(hash, salt, ikm);
    // Step 2: "expand" prk to several pseudo-random keys(okm)
    let t = Buffer.alloc(0);
    let okm = Buffer.alloc(0);
    for (let i = 0; i < Math.ceil(length / prk.length); ++i) {
      t = this.hmac(hash, prk, Buffer.concat([t, info, Buffer.alloc(1, i + 1)]));
      okm = Buffer.concat([okm, t]);
    }
    // Step 3: crop okm to desired length
    return okm.slice(0, length);
  }

}
<|endoftext|>


<commit_before>'use strict'

var dgram = require('dgram')
var transports = require('stun-js').transports
var TurnClient = require('../lib/turn_client')

var chai = require('chai')
var chaiAsPromised = require('chai-as-promised')
var expect = chai.expect
chai.use(chaiAsPromised)
chai.should()

var turnAddr = process.env.TURN_ADDR
var turnPort = process.env.TURN_PORT
var turnUser = process.env.TURN_USER
var turnPwd = process.env.TURN_PASS
var socketPort = 33333

var winston = require('winston')
winston.level = 'debug'

describe('#TURN operations', function () {
  this.timeout(15000)

  // it('should execute TURN allocate operation over UDP socket using promises', function (done) {
  //   var retransmissionTimer
  //   // send a TURN allocate request and verify the reply
  //   var sendAllocateRequest = function (client, socket) {
  //     client.allocateP()
  //       .then(function (result) {
  //         // end retransmissionTimer
  //         clearTimeout(retransmissionTimer)
  //         // test results
  //         expect(result).not.to.be.undefined
  //         expect(result).to.have.property('mappedAddress')
  //         expect(result.mappedAddress).to.have.property('address')
  //         expect(result.mappedAddress).to.have.property('port')
  //         // expect(result.mappedAddress.address).to.equal(testGW)
  //         expect(result).to.have.property('relayedAddress')
  //         expect(result.relayedAddress).to.have.property('address')
  //         expect(result.relayedAddress).to.have.property('port')
  //         // expect(result.relayedAddress.address).to.equal(turnAddr)
  //         return client.closeP()
  //       })
  //       .then(function () {
  //         expect(socket.listeners('message').length).to.equal(1)
  //         expect(socket.listeners('error').length).to.equal(1)
  //         // close socket
  //         socket.close(function () {
  //           done()
  //         })
  //       })
  //       .catch(function (error) {
  //         done(error)
  //       })
  //   }
  //   // create socket
  //   var socket = dgram.createSocket('udp4')
  //   socket.on('message', function (message, rinfo) { //
  //     done(new Error('message callback should not be fired'))
  //   })
  //   socket.on('error', function (error) {
  //     done(error)
  //   })
  //   socket.on('listening', function () {
  //     // create turn client and pass socket over
  //     var transport = new transports.UDP(socket)
  //     var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
  //     // retransmission timer -- we're using UDP ...
  //     retransmissionTimer = setTimeout(function () {
  //       console.log('resending ALLOCATE request')
  //       sendAllocateRequest(client, socket)
  //     }, 5000)
  //     // allocate request
  //     sendAllocateRequest(client, socket)
  //   })
  //   socket.bind(socketPort)
  // })
  //
  // it('should execute TURN allocate operation over TCP socket using callbacks', function (done) {
  //   var transport = new transports.TCP()
  //   var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
  //   var onError = function (error) {
  //     done(error)
  //   }
  //   var onReady = function (result) {
  //     expect(result).not.to.be.undefined
  //     expect(result).to.have.property('mappedAddress')
  //     expect(result.mappedAddress).to.have.property('address')
  //     expect(result.mappedAddress).to.have.property('port')
  //     // expect(result.mappedAddress.address).to.equal(testGW)
  //     expect(result).to.have.property('relayedAddress')
  //     expect(result.relayedAddress).to.have.property('address')
  //     expect(result.relayedAddress).to.have.property('port')
  //     // expect(result.relayedAddress.address).to.equal(turnAddr)
  //     client.close(
  //       function () {
  //         done()
  //       },
  //       onError
  //     )
  //   }
  //   client.allocate(onReady, onError)
  // })
  //
  // it('should execute TURN allocate operation over unspecified UDP socket using promises', function (done) {
  //   var retransmissionTimer
  //   // send a TURN allocate request and verify the reply
  //   var sendAllocateRequest = function (client) {
  //     client.allocateP()
  //       .then(function (result) {
  //         // end retransmissionTimer
  //         clearTimeout(retransmissionTimer)
  //         // test results
  //         expect(result).not.to.be.undefined
  //         expect(result).to.have.property('mappedAddress')
  //         expect(result.mappedAddress).to.have.property('address')
  //         expect(result.mappedAddress).to.have.property('port')
  //         // expect(result.mappedAddress.address).to.equal(testGW)
  //         expect(result).to.have.property('relayedAddress')
  //         expect(result.relayedAddress).to.have.property('address')
  //         expect(result.relayedAddress).to.have.property('port')
  //         // expect(result.relayedAddress.address).to.equal(turnAddr)
  //         return client.closeP()
  //       })
  //       .then(function () {
  //         done()
  //       })
  //       .catch(function (error) {
  //         done(error)
  //       })
  //   }
  //   // create turn client
  //   var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd)
  //   // retransmission timer -- we're using UDP ...
  //   retransmissionTimer = setTimeout(function () {
  //     console.log('resending ALLOCATE request')
  //     sendAllocateRequest(client)
  //   }, 5000)
  //   // allocate request
  //   sendAllocateRequest(client)
  // })
  //
  // it('should execute TURN allocate followed by refresh over UDP socket using promises', function (done) {
  //   var lifetime = 3600
  //   var retransmissionTimer
  //   // send a TURN allocate request and verify the reply
  //   var sendAllocateAndRefreshRequest = function (client) {
  //     client.allocateP()
  //       .then(function (result) {
  //         return client.refreshP(lifetime)
  //       })
  //       .then(function (duration) {
  //         // end retransmissionTimer
  //         clearTimeout(retransmissionTimer)
  //         // test results
  //         expect(duration).to.equal(lifetime)
  //         // close turn client
  //         return client.closeP()
  //       })
  //       .then(function () {
  //         expect(socket.listeners('message').length).to.equal(1)
  //         expect(socket.listeners('error').length).to.equal(1)
  //         done()
  //       })
  //       .catch(function (error) {
  //         done(error)
  //       })
  //   }
  //   // create socket
  //   var socket = dgram.createSocket('udp4')
  //   socket.on('message', function (message, rinfo) { //
  //     done(new Error('message callback should not be fired'))
  //   })
  //   socket.on('error', function (error) {
  //     done(error)
  //   })
  //   socket.on('listening', function () {
  //     // create stun client and pass socket over
  //     var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd)
  //     // retransmission timer -- we're using UDP ...
  //     retransmissionTimer = setTimeout(function () {
  //       console.log('resending ALLOCATE and REFRESH request')
  //       sendAllocateAndRefreshRequest(client)
  //     }, 5000)
  //     sendAllocateAndRefreshRequest(client)
  //   })
  //   socket.bind(socketPort)
  // })
  //
  // it('should execute TURN allocate followed by create permission over TCP socket using promises', function () {
  //   var transport = new transports.TCP()
  //   var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
  //   var turnAddress = '1.2.3.4'
  //   return client.allocateP()
  //     .then(function (result) {
  //       return client.createPermissionP(turnAddress)
  //     })
  //     .then(function () {
  //       return client.closeP()
  //     })
  // })
  //
  // it('should execute TURN allocate followed by two consecutive create permissions (testing permission refresh) over TCP socket using promises', function () {
  //   var transport = new transports.TCP()
  //   var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
  //   var turnAddress = '1.2.3.4'
  //   return client.allocateP()
  //     .then(function (result) {
  //       return client.createPermissionP(turnAddress)
  //     })
  //     .then(function () {
  //       return client.createPermissionP(turnAddress)
  //     })
  //     .then(function () {
  //       return client.closeP()
  //     })
  // })

  it('should receive messages that are sent via relay server over TCP sockets', function (done) {
    var testData = 'hello there'
    var testRuns = 10
    var messagesReceived = 0

    // alice's client
    var clientAlice = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    // bob's client
    var clientBob = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    var srflxAddressAlice, srflxAddressBob, relayAddressAlice, relayAddressBob

    var sendTestMessageFromAliceToBob = function () {
      var bytes = new Buffer(testData)
      clientAlice.sendToRelay(
        bytes,
        relayAddressBob.address,
        relayAddressBob.port,
        function () {
          console.log('message sent to ' + relayAddressBob.address + ':' + relayAddressBob.port)
        }, // on success
        function (error) {
          done(error)
        }
      )
    }

    // subscribe to incoming messages
    clientBob.on('relayed-message', function (bytes, peerAddress) {
      var message = bytes.toString()
      expect(message).to.equal(testData)
      console.log('receiving test message ' + message)
      messagesReceived++
      if (messagesReceived === testRuns) {
        clientBob.closeP()
          .then(function () {
            return clientAlice.closeP()
          })
          .then(function () {
            done()
          })
          .catch(function (error) {
            done(error)
          })
      } else {
        sendTestMessageFromAliceToBob()
      }
    })

    // allocate relaying session for alice
    clientAlice.allocateP()
      .then(function (allocateAddress) {
        srflxAddressAlice = allocateAddress.mappedAddress
        relayAddressAlice = allocateAddress.relayedAddress
        console.log("alice's srflx address = " + srflxAddressAlice.address + ':' + srflxAddressAlice.port)
        console.log("alice's relay address = " + relayAddressAlice.address + ':' + relayAddressAlice.port)
        // allocate relaying session for bob
        return clientBob.allocateP()
      })
      .then(function (allocateAddress) {
        srflxAddressBob = allocateAddress.mappedAddress
        relayAddressBob = allocateAddress.relayedAddress
        console.log("bob's address = " + srflxAddressBob.address + ':' + srflxAddressBob.port)
        console.log("bob's relay address = " + relayAddressBob.address + ':' + relayAddressBob.port)
        // create permission for alice to send messages to bob
        return clientBob.createPermissionP(relayAddressAlice.address)
      })
      .then(function () {
        // create permission for bob to send messages to alice
        return clientAlice.createPermissionP(relayAddressBob.address)
      })
      .then(function () {
        // send test message
        sendTestMessageFromAliceToBob()
      })
      .catch(function (error) {
        done(error)
      })
  })

  it('should execute TURN channel binding and receive messages sent via these channels over TCP sockets using promises', function (done) {
    var testData = 'hello there'
    var testRuns = 10
    var messagesReceived = 0

    // alice's client
    var clientAlice = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    // bob's client
    var clientBob = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    var srflxAddressAlice, srflxAddressBob, relayAddressAlice, relayAddressBob
    var channelId

    var sendTestMessageFromAliceToBob = function () {
      var bytes = new Buffer(testData)
      clientAlice.sendToChannel(
        bytes,
        channelId,
        function () {
          console.log('message sent to channel ' + channelId)
        },
        function (error) {
          done(error)
        }
      )
    }

    // subscribe to incoming messages
    clientBob.on('relayed-message', function (bytes, peerAddress) {
      var message = bytes.toString()
      expect(message).to.equal(testData)
      console.log('receiving test message ' + message)
      messagesReceived++
      if (messagesReceived === testRuns) {
        clientBob.closeP()
          .then(function () {
            return clientAlice.closeP()
          })
          .then(function () {
            done()
          })
          .catch(function (error) {
            done(error)
          })
      } else {
        sendTestMessageFromAliceToBob()
      }
    })

    // allocate relaying session for alice
    clientAlice.allocateP()
      .then(function (allocateAddress) {
        srflxAddressAlice = allocateAddress.mappedAddress
        relayAddressAlice = allocateAddress.relayedAddress
        console.log("alice's srflx address = " + srflxAddressAlice.address + ':' + srflxAddressAlice.port)
        console.log("alice's relay address = " + relayAddressAlice.address + ':' + relayAddressAlice.port)
        // allocate relaying session for bob
        return clientBob.allocateP()
      })
      .then(function (allocateAddress) {
        srflxAddressBob = allocateAddress.mappedAddress
        relayAddressBob = allocateAddress.relayedAddress
        console.log("bob's address = " + srflxAddressBob.address + ':' + srflxAddressBob.port)
        console.log("bob's relay address = " + relayAddressBob.address + ':' + relayAddressBob.port)
        // create permission for alice to send messages to bob
        return clientBob.createPermissionP(relayAddressAlice.address)
      })
      .then(function () {
        // create channel from alice to bob
        return clientAlice.bindChannelP(relayAddressBob.address, relayAddressBob.port)
      })
      .then(function (channel) {
        expect(channel).not.to.be.undefined
        channelId = channel
        // mimic refreshing of channel binding
        return clientAlice.bindChannelP(relayAddressBob.address, relayAddressBob.port, channel)
      })
      .then(function (channel) {
        expect(channel).to.equal(channelId)
        //  create permission for bob to send messages to alice
        return clientAlice.createPermissionP(relayAddressBob.address)
      })
      .then(function () {
        // create channel from bob to alice
        return clientBob.bindChannelP(relayAddressAlice.address, relayAddressAlice.port)
      })
      .then(function (anotherChannel) {
        // send test message
        sendTestMessageFromAliceToBob()
      })
      .catch(function (error) {
        done(error)
      })
  })
})
<commit_msg>re-activating all test cases<commit_after>'use strict'

var dgram = require('dgram')
var transports = require('stun-js').transports
var TurnClient = require('../lib/turn_client')

var chai = require('chai')
var chaiAsPromised = require('chai-as-promised')
var expect = chai.expect
chai.use(chaiAsPromised)
chai.should()

var turnAddr = process.env.TURN_ADDR
var turnPort = process.env.TURN_PORT
var turnUser = process.env.TURN_USER
var turnPwd = process.env.TURN_PASS
var socketPort = 33333

var winston = require('winston')
winston.level = 'debug'

describe('#TURN operations', function () {
  this.timeout(15000)

  it('should execute TURN allocate operation over UDP socket using promises', function (done) {
    var retransmissionTimer
    // send a TURN allocate request and verify the reply
    var sendAllocateRequest = function (client, socket) {
      client.allocateP()
        .then(function (result) {
          // end retransmissionTimer
          clearTimeout(retransmissionTimer)
          // test results
          expect(result).not.to.be.undefined
          expect(result).to.have.property('mappedAddress')
          expect(result.mappedAddress).to.have.property('address')
          expect(result.mappedAddress).to.have.property('port')
          // expect(result.mappedAddress.address).to.equal(testGW)
          expect(result).to.have.property('relayedAddress')
          expect(result.relayedAddress).to.have.property('address')
          expect(result.relayedAddress).to.have.property('port')
          // expect(result.relayedAddress.address).to.equal(turnAddr)
          return client.closeP()
        })
        .then(function () {
          expect(socket.listeners('message').length).to.equal(1)
          expect(socket.listeners('error').length).to.equal(1)
          // close socket
          socket.close(function () {
            done()
          })
        })
        .catch(function (error) {
          done(error)
        })
    }
    // create socket
    var socket = dgram.createSocket('udp4')
    socket.on('message', function (message, rinfo) { //
      done(new Error('message callback should not be fired'))
    })
    socket.on('error', function (error) {
      done(error)
    })
    socket.on('listening', function () {
      // create turn client and pass socket over
      var transport = new transports.UDP(socket)
      var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
      // retransmission timer -- we're using UDP ...
      retransmissionTimer = setTimeout(function () {
        console.log('resending ALLOCATE request')
        sendAllocateRequest(client, socket)
      }, 5000)
      // allocate request
      sendAllocateRequest(client, socket)
    })
    socket.bind(socketPort)
  })

  it('should execute TURN allocate operation over TCP socket using callbacks', function (done) {
    var transport = new transports.TCP()
    var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
    var onError = function (error) {
      done(error)
    }
    var onReady = function (result) {
      expect(result).not.to.be.undefined
      expect(result).to.have.property('mappedAddress')
      expect(result.mappedAddress).to.have.property('address')
      expect(result.mappedAddress).to.have.property('port')
      // expect(result.mappedAddress.address).to.equal(testGW)
      expect(result).to.have.property('relayedAddress')
      expect(result.relayedAddress).to.have.property('address')
      expect(result.relayedAddress).to.have.property('port')
      // expect(result.relayedAddress.address).to.equal(turnAddr)
      client.close(
        function () {
          done()
        },
        onError
      )
    }
    client.allocate(onReady, onError)
  })

  it('should execute TURN allocate operation over unspecified UDP socket using promises', function (done) {
    var retransmissionTimer
    // send a TURN allocate request and verify the reply
    var sendAllocateRequest = function (client) {
      client.allocateP()
        .then(function (result) {
          // end retransmissionTimer
          clearTimeout(retransmissionTimer)
          // test results
          expect(result).not.to.be.undefined
          expect(result).to.have.property('mappedAddress')
          expect(result.mappedAddress).to.have.property('address')
          expect(result.mappedAddress).to.have.property('port')
          // expect(result.mappedAddress.address).to.equal(testGW)
          expect(result).to.have.property('relayedAddress')
          expect(result.relayedAddress).to.have.property('address')
          expect(result.relayedAddress).to.have.property('port')
          // expect(result.relayedAddress.address).to.equal(turnAddr)
          return client.closeP()
        })
        .then(function () {
          done()
        })
        .catch(function (error) {
          done(error)
        })
    }
    // create turn client
    var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd)
    // retransmission timer -- we're using UDP ...
    retransmissionTimer = setTimeout(function () {
      console.log('resending ALLOCATE request')
      sendAllocateRequest(client)
    }, 5000)
    // allocate request
    sendAllocateRequest(client)
  })

  it('should execute TURN allocate followed by refresh over UDP socket using promises', function (done) {
    var lifetime = 3600
    var retransmissionTimer
    // send a TURN allocate request and verify the reply
    var sendAllocateAndRefreshRequest = function (client) {
      client.allocateP()
        .then(function (result) {
          return client.refreshP(lifetime)
        })
        .then(function (duration) {
          // end retransmissionTimer
          clearTimeout(retransmissionTimer)
          // test results
          expect(duration).to.equal(lifetime)
          // close turn client
          return client.closeP()
        })
        .then(function () {
          expect(socket.listeners('message').length).to.equal(1)
          expect(socket.listeners('error').length).to.equal(1)
          done()
        })
        .catch(function (error) {
          done(error)
        })
    }
    // create socket
    var socket = dgram.createSocket('udp4')
    socket.on('message', function (message, rinfo) { //
      done(new Error('message callback should not be fired'))
    })
    socket.on('error', function (error) {
      done(error)
    })
    socket.on('listening', function () {
      // create stun client and pass socket over
      var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd)
      // retransmission timer -- we're using UDP ...
      retransmissionTimer = setTimeout(function () {
        console.log('resending ALLOCATE and REFRESH request')
        sendAllocateAndRefreshRequest(client)
      }, 5000)
      sendAllocateAndRefreshRequest(client)
    })
    socket.bind(socketPort)
  })

  it('should execute TURN allocate followed by create permission over TCP socket using promises', function () {
    var transport = new transports.TCP()
    var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
    var turnAddress = '1.2.3.4'
    return client.allocateP()
      .then(function (result) {
        return client.createPermissionP(turnAddress)
      })
      .then(function () {
        return client.closeP()
      })
  })

  it('should execute TURN allocate followed by two consecutive create permissions (testing permission refresh) over TCP socket using promises', function () {
    var transport = new transports.TCP()
    var client = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, transport)
    var turnAddress = '1.2.3.4'
    return client.allocateP()
      .then(function (result) {
        return client.createPermissionP(turnAddress)
      })
      .then(function () {
        return client.createPermissionP(turnAddress)
      })
      .then(function () {
        return client.closeP()
      })
  })

  it('should receive messages that are sent via relay server over TCP sockets', function (done) {
    var testData = 'hello there'
    var testRuns = 10
    var messagesReceived = 0

    // alice's client
    var clientAlice = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    // bob's client
    var clientBob = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    var srflxAddressAlice, srflxAddressBob, relayAddressAlice, relayAddressBob

    var sendTestMessageFromAliceToBob = function () {
      var bytes = new Buffer(testData)
      clientAlice.sendToRelay(
        bytes,
        relayAddressBob.address,
        relayAddressBob.port,
        function () {
          console.log('message sent to ' + relayAddressBob.address + ':' + relayAddressBob.port)
        }, // on success
        function (error) {
          done(error)
        }
      )
    }

    // subscribe to incoming messages
    clientBob.on('relayed-message', function (bytes, peerAddress) {
      var message = bytes.toString()
      expect(message).to.equal(testData)
      console.log('receiving test message ' + message)
      messagesReceived++
      if (messagesReceived === testRuns) {
        clientBob.closeP()
          .then(function () {
            return clientAlice.closeP()
          })
          .then(function () {
            done()
          })
          .catch(function (error) {
            done(error)
          })
      } else {
        sendTestMessageFromAliceToBob()
      }
    })

    // allocate relaying session for alice
    clientAlice.allocateP()
      .then(function (allocateAddress) {
        srflxAddressAlice = allocateAddress.mappedAddress
        relayAddressAlice = allocateAddress.relayedAddress
        console.log("alice's srflx address = " + srflxAddressAlice.address + ':' + srflxAddressAlice.port)
        console.log("alice's relay address = " + relayAddressAlice.address + ':' + relayAddressAlice.port)
        // allocate relaying session for bob
        return clientBob.allocateP()
      })
      .then(function (allocateAddress) {
        srflxAddressBob = allocateAddress.mappedAddress
        relayAddressBob = allocateAddress.relayedAddress
        console.log("bob's address = " + srflxAddressBob.address + ':' + srflxAddressBob.port)
        console.log("bob's relay address = " + relayAddressBob.address + ':' + relayAddressBob.port)
        // create permission for alice to send messages to bob
        return clientBob.createPermissionP(relayAddressAlice.address)
      })
      .then(function () {
        // create permission for bob to send messages to alice
        return clientAlice.createPermissionP(relayAddressBob.address)
      })
      .then(function () {
        // send test message
        sendTestMessageFromAliceToBob()
      })
      .catch(function (error) {
        done(error)
      })
  })

  it('should execute TURN channel binding and receive messages sent via these channels over TCP sockets using promises', function (done) {
    var testData = 'hello there'
    var testRuns = 10
    var messagesReceived = 0

    // alice's client
    var clientAlice = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    // bob's client
    var clientBob = new TurnClient(turnAddr, turnPort, turnUser, turnPwd, new transports.TCP())
    var srflxAddressAlice, srflxAddressBob, relayAddressAlice, relayAddressBob
    var channelId

    var sendTestMessageFromAliceToBob = function () {
      var bytes = new Buffer(testData)
      clientAlice.sendToChannel(
        bytes,
        channelId,
        function () {
          console.log('message sent to channel ' + channelId)
        },
        function (error) {
          done(error)
        }
      )
    }

    // subscribe to incoming messages
    clientBob.on('relayed-message', function (bytes, peerAddress) {
      var message = bytes.toString()
      expect(message).to.equal(testData)
      console.log('receiving test message ' + message)
      messagesReceived++
      if (messagesReceived === testRuns) {
        clientBob.closeP()
          .then(function () {
            return clientAlice.closeP()
          })
          .then(function () {
            done()
          })
          .catch(function (error) {
            done(error)
          })
      } else {
        sendTestMessageFromAliceToBob()
      }
    })

    // allocate relaying session for alice
    clientAlice.allocateP()
      .then(function (allocateAddress) {
        srflxAddressAlice = allocateAddress.mappedAddress
        relayAddressAlice = allocateAddress.relayedAddress
        console.log("alice's srflx address = " + srflxAddressAlice.address + ':' + srflxAddressAlice.port)
        console.log("alice's relay address = " + relayAddressAlice.address + ':' + relayAddressAlice.port)
        // allocate relaying session for bob
        return clientBob.allocateP()
      })
      .then(function (allocateAddress) {
        srflxAddressBob = allocateAddress.mappedAddress
        relayAddressBob = allocateAddress.relayedAddress
        console.log("bob's address = " + srflxAddressBob.address + ':' + srflxAddressBob.port)
        console.log("bob's relay address = " + relayAddressBob.address + ':' + relayAddressBob.port)
        // create permission for alice to send messages to bob
        return clientBob.createPermissionP(relayAddressAlice.address)
      })
      .then(function () {
        // create channel from alice to bob
        return clientAlice.bindChannelP(relayAddressBob.address, relayAddressBob.port)
      })
      .then(function (channel) {
        expect(channel).not.to.be.undefined
        channelId = channel
        // mimic refreshing of channel binding
        return clientAlice.bindChannelP(relayAddressBob.address, relayAddressBob.port, channel)
      })
      .then(function (channel) {
        expect(channel).to.equal(channelId)
        //  create permission for bob to send messages to alice
        return clientAlice.createPermissionP(relayAddressBob.address)
      })
      .then(function () {
        // create channel from bob to alice
        return clientBob.bindChannelP(relayAddressAlice.address, relayAddressAlice.port)
      })
      .then(function (anotherChannel) {
        // send test message
        sendTestMessageFromAliceToBob()
      })
      .catch(function (error) {
        done(error)
      })
  })
})
<|endoftext|>


<commit_before>#!/usr/local/bin/node --harmony

// Test co / yield

var co = require('co');
var grid = require('../../lib/ugrid-client.js')({data: {type: 't000'}});

var closed = false;

process.on('exit', function () {
	console.assert(closed);
	console.assert(grid.id !== undefined);
});

grid.on('close', function () {closed = true;});

grid.on('request', function (msg) {
	grid.reply(msg, null, 'hello ' + msg.data);
});

co(function *() {
	var devices = yield grid.devices({type: 't000'});
	console.assert(devices[0] != undefined);
	var id = yield grid.send(0, {cmd: 'id', data: grid.uuid});
	console.assert(id == grid.id);
	var response = yield grid.request(devices[0], 'test');
	console.assert(response == 'hello test');
	var data = yield grid.get(grid.uuid);
	console.assert(data.data.type === 't000');
	grid.set({type: 't001', state: 'ok'});
	data = yield grid.get(grid.uuid);
	console.assert(data.data.type === 't001');
	console.assert(data.data.state === 'ok');
	grid.end();
})();
<commit_msg>fix test/ugridClient/t004.js<commit_after>#!/usr/local/bin/node --harmony

// Test co / yield

var co = require('co');
var grid = require('../../lib/ugrid-client.js')({data: {type: 't000'}});

var closed = false;

process.on('exit', function () {
	console.assert(closed);
	console.assert(grid.id !== undefined);
});

grid.on('close', function () {closed = true;});

grid.on('request', function (msg) {
	grid.reply(msg, null, 'hello ' + msg.data);
});

co(function *() {
	var devices = yield grid.devices({type: 't000'});
	console.assert(devices[0] != undefined);
	var id = yield grid.send(0, {cmd: 'id', data: grid.uuid});
	console.assert(id == grid.id);
	var response = yield grid.request(devices[0], 'test');
	console.assert(response == 'hello test');
	var data = yield grid.get(grid.uuid);
	console.assert(data.type === 't000');
	grid.set({type: 't001', state: 'ok'});
	data = yield grid.get(grid.uuid);
	console.assert(data.type === 't001');
	console.assert(data.state === 'ok');
	grid.end();
})();
<|endoftext|>