bugout
Version:
Backend web services over WebRTC.
354 lines (292 loc) • 11.1 kB
JavaScript
var test = require("tape");
var WebTorrent = require("webtorrent");
var Bugout = require("./index.js");
var wtest = new WebTorrent({dht: false, tracker: false});
var wtest2 = new WebTorrent({dht: false, tracker: false});
var wtest3 = new WebTorrent({dht: false, tracker: false});
// PRS WELCOME
//
// TODO: test RPC with each type of argument combination
// TODO: test bad RPC with unknown nonce
// TODO: more bad parameters & calls
// TODO: check mutated keys yield cryptographic errors
// TODO: test malformed JSON packets
test.onFinish(function() {
wtest.destroy();
wtest2.destroy();
wtest3.destroy();
});
test('Instantiation', function (t) {
t.plan(12);
var b1 = new Bugout({seed: "BohNtZ24TrgMwZTLx9VDKtcZARNVuCt5tnecAAxYtTBC8pC61uGN", wt: wtest});
t.equal(b1.identifier, "bYSkTy24xXJj6dWe79ZAQXKJZrn2n983SQ", "server identifier");
t.equal(b1.pk, "CXENBY9X3x5TN1yjRyu1U1WkGuujuVBNiqxA16oAYbFo", "server public key");
t.equal(b1.identifier, Bugout.address("CXENBY9X3x5TN1yjRyu1U1WkGuujuVBNiqxA16oAYbFo"), "server address from pk");
t.equal(b1.identifier, Bugout.address(b1.keyPair.publicKey), "server address from pk array");
b1.torrent.on("infoHash", function() {
t.equal(b1.torrent.infoHash, "28d878040b7d2f5215409373b415fb99bc0e6d88", "server infoHash");
});
t.throws(function() {
var b2 = new Bugout({seed: "BohNtZ24TrgMwZTLx9VDLtcZARNVuCt5tnecAAxYtTBC8pC61uGN", wt: wtest});
}, "Error: Invalid checksum", "invalid seed checksum");
var b3 = new Bugout("bMuHpwCxcD5vhC5u7VKuajYu5RU7FUnaGJ", {wt: wtest});
t.equal(b3.identifier, "bMuHpwCxcD5vhC5u7VKuajYu5RU7FUnaGJ", "client identifier");
b3.torrent.on("infoHash", function() {
t.equal(b3.torrent.infoHash, "d96fe55834a62d86e48573c132345c01a38f5ffd", "client infoHash");
});
var b4 = new Bugout({seed: Bugout.encodeseed(Array(32).fill(0x23)), wt: wtest});
console.log(b4.address());
t.equal(b4.identifier, "bYwqSagZb5n42M9qXSw2uu3Cpxg9JhZcnd", "encode seed client identifier");
b4.torrent.on("infoHash", function() {
console.log(b4.torrent.infoHash);
t.equal(b4.torrent.infoHash, "5486696a87e91c6c7fcfc6279c9b08709c7aa61f", "client infoHash");
});
var b5 = new Bugout({torrent: b4.torrent});
t.equal(b5.torrentCreated, false);
b5.destroy();
t.equal(b4.torrent, b5.torrent);
});
test("Connectivity events", function(t) {
t.plan(7);
var bs = new Bugout({wt: wtest});
var bc = new Bugout(bs.address(), {wt: wtest2});
var clast = null;
var times = 2;
function connectioncounter(c) {
t.notEqual(clast, c, "connection count");
times -= 1;
clast = c;
if (!times) {
bs.removeListener("connections", connectioncounter);
}
}
bs.on("connections", connectioncounter);
bs.connections();
bs.connections();
bc.on("wireseen", function(c) {
t.equal(c, 1, "client wire count");
});
bs.on("wireseen", function(c) {
t.equal(c, 1, "server wire count");
});
bc.on("seen", function(address) {
t.equal(address, bs.address(), "client remote address");
});
bs.on("seen", function(address) {
t.equal(address, bc.address(), "server remote address");
});
bc.on("server", function(address) {
t.equal(address, bs.address(), "server seen correct address");
});
// connect the two clients together
bs.torrent.on("infoHash", function() {
bs.torrent.addPeer("127.0.0.1:" + bc.wt.address().port);
});
});
test("RPC and message passing", function(t) {
t.plan(7);
var bs = new Bugout({wt: wtest});
var bc = new Bugout(bs.address(), {wt: wtest2});
var msg = {"Hello": "world"};
bs.register("ping", function(address, args, cb) {
t.equal(address, bc.address(), "client rpc address");
args["pong"] = true;
cb(args);
});
bs.register("rpc", console.log.bind(null, "rpc"));
bs.on("seen", function(address) {
t.equal(address, bc.address(), "server seen client address");
bs.send(address, {"Hello": "world"});
});
bc.on("server", function(address) {
t.equal(address, bs.address(), "client seen server address");
bc.rpc("ping", msg, function(response) {
t.equal(response.Hello, "world", "RPC server response check value");
t.ok(response.pong, "RPC server response check pong");
});
});
bc.on("message", function(address, message) {
t.equal(address, bs.address(), "server message remote address");
t.deepEqual(message, msg, "server message content check");
});
// connect the two clients together
bs.torrent.on("infoHash", function() {
bs.torrent.addPeer("127.0.0.1:" + bc.wt.address().port);
});
});
test("3 party incomplete graph gossip test", function(t) {
t.plan(10);
var bs = new Bugout({wt: wtest});
var bc1 = new Bugout(bs.address(), {wt: wtest2});
var bc2 = new Bugout(bs.address(), {wt: wtest3});
var msg = {"Foo": "bar", "meaning": 42};
bs.register("ping", function(address, args, cb) {
t.equal(address, bc2.address(), "client rpc address");
args["pong"] = true;
cb(args);
});
bs.on("rpc", function(address, call, args) {
// check rpc was from client2
t.equal(bc2.address(), address, "server check client2 was rpc sender");
});
// this should never fire
bc1.on("rpc", console.log.bind(null, "client1 rpc"));
bc2.on("server", function(address) {
t.equal(address, bs.address(), "client2 seen server address");
// verify we're only acutally connected to other client
// (getting messages by gossip)
t.equal(bc2.torrent.wires.length, 1, "client2 only one wire");
t.equal(bc2.address(bc2.torrent.wires[0].peerExtendedHandshake.pk.toString()), bc1.address(), "client2 is connected to client1");
bc2.rpc("ping", msg, function(response) {
t.equal(response.Foo, "bar", "RPC server response check value 1");
t.equal(response.meaning, 42, "RPC server response check value 2");
t.ok(response.pong, "RPC server response check pong");
});
});
// connect first client to server
bs.torrent.on("infoHash", function() {
bs.torrent.addPeer("127.0.0.1:" + bc1.wt.address().port);
bs.once("seen", function(address) {
t.equal(address, bc1.address(), "server seen client1 address");
// bs.send(address, msg);
// check the second client's connection
bs.once("seen", function(address) {
t.equal(address, bc2.address(), "server seen client2 address");
});
// connect second client to first
setTimeout(function() {
bc1.torrent.addPeer("127.0.0.1:" + bc2.wt.address().port);
}, 100);
});
});
});
test("heartbeat seen and timeout", function(t) {
t.plan(20);
var interval = 100;
var timeout = 1000;
var bs = new Bugout({wt: wtest, heartbeat: interval, timeout: timeout});
var bc1 = new Bugout(bs.address(), {wt: wtest2, heartbeat: interval, timeout: timeout});
var bc2 = new Bugout(bs.address(), {wt: wtest3, heartbeat: interval, timeout: timeout});
/*console.log(" ->>> bs:", bs.address());
console.log(" ->>> bc1:", bc1.address());
console.log(" ->>> bc2:", bc2.address());*/
/*bs.on("timeout", console.log.bind(null, "-> bs timeout"));
bc2.on("timeout", console.log.bind(null, "-> bc2 timeout"));
bs.on("left", console.log.bind(null, "-> bs left"));
bc2.on("left", console.log.bind(null, "-> bc2 left"));
bs.on("ping", console.log.bind(null, "bs ping"));
bc1.on("ping", console.log.bind(null, "bc1 ping"));
bc2.on("ping", console.log.bind(null, "bc2 ping"));*/
var pingers = [
[bc2.address(), bc1.address(), bs],
[bs.address(), bc2.address(), bc1],
[bs.address(), bc1.address(), bc2],
];
// ensure each client receives at least one ping from each other client
pingers.map(function(pingtest) {
var src = pingtest.pop();
var expected = {};
for (var p=0; p<pingtest.length; p++) {
expected[pingtest[p]] = true;
}
src.on("ping", function(address) {
if (expected[address]) {
t.pass("ping from " + address);
delete expected[address];
}
});
});
// ensure server sees client 2 timeout
bs.on("timeout", function(address) {
// ignore bc1 timeout
if (address == bc2.address()) {
t.pass("server saw client2 timeout");
}
});
// ensure client2 sees server timeout
bc2.on("timeout", function(address) {
// ignore bc1 timeout
if (address == bs.address()) {
t.pass("client2 saw server timeout");
}
});
var leavers = [
[bc2.address(), bc1.address(), bs],
[bs.address(), bc1.address(), bc2],
];
// bs and bc2 should see the other two leave each once
leavers.map(function(leavetest) {
var src = leavetest.pop();
var expected = {};
for (var e=0; e<leavetest.length; e++) {
expected[leavetest[e]] = true;
}
src.on("left", function(address) {
if (expected[address]) {
t.pass(address + " left");
delete expected[address];
} else {
t.fail(address + " left unexpectedly");
}
// clean up once this test is done
if (Object.keys(expected).length == 0) {
src.destroy();
}
});
});
var msg = {"Goober": "dougal", "question": 42};
bc2.on("server", function(address) {
t.equal(address, bs.address(), "client2 seen server address");
// verify we're only acutally connected to other client
// (getting messages by gossip)
t.equal(bc2.torrent.wires.length, 1, "client2 only one wire");
t.equal(bc2.address(bc2.torrent.wires[0].peerExtendedHandshake.pk.toString()), bc1.address(), "client2 is connected to client1");
bs.on("wireleft", function() {
t.equal(bs.torrent.wires.length, 0, "server wires to zero");
});
bc2.on("wireleft", function() {
t.equal(bc2.torrent.wires.length, 0, "client2 wires to zero");
});
// disconnect bc2
setTimeout(function() {
bc1.destroy(function() {
t.pass("bc1 destroyed");
});
}, 500);
});
// connect first client to server
bs.torrent.on("infoHash", function() {
bs.torrent.addPeer("127.0.0.1:" + bc1.wt.address().port);
bs.once("seen", function(address) {
t.equal(address, bc1.address(), "server seen client1 address");
// bs.send(address, msg);
// check the second client's connection
bs.once("seen", function(address) {
t.equal(address, bc2.address(), "server seen client2 address");
});
// connect second client to first
setTimeout(function() {
bc1.torrent.addPeer("127.0.0.1:" + bc2.wt.address().port);
}, 100);
});
});
});
test("RPC response handles async callback", {"timeout": 3000}, function(t) {
t.plan(1);
var bs = new Bugout({wt: wtest});
var bc = new Bugout(bs.address(), {wt: wtest2});
bs.register("ping", function(address, args, cb) {
args["pong"] = true;
setTimeout(function() {
cb(args);
}, 1000);
});
bc.on("server", function(address) {
bc.rpc("ping", {}, function(response) {
t.ok(response.pong, "RPC server response check async pong");
});
});
bs.torrent.on("infoHash", function() {
bs.torrent.addPeer("127.0.0.1:" + bc.wt.address().port);
});
});