var fs = require('fs') var test = require('tape') var spawn = require('child_process').spawn var exec = require('child_process').exec var crypto = require('crypto') var net = require('net') var mkdirp = require('mkdirp') var join = require('path').join var ma = require('multiserver-address') // travis currently does not support ipv6, becaue GCE does not. var has_ipv6 = process.env.TRAVIS === undefined var children = [] process.on('exit', function () { children.forEach(function (e) { e.kill('SIGKILL') }) }) process.on('SIGINT', function () { children.forEach(function (e) { e.kill('SIGKILL') }) process.exit(1) }) var exited = false var count = 0 function ssbServer(t, argv, opts) { count ++ exited = false opts = opts || {} var sh = spawn( process.execPath, [join(__dirname, '../bin.js')] .concat(argv), Object.assign({ env: Object.assign({}, process.env, {ssb_appname: 'test'}), }, opts) ) sh.once('exit', function (code, name) { exited = true t.equal(name,'SIGKILL') if(--count) return t.end() }) sh.stdout.pipe(process.stdout) sh.stderr.pipe(process.stderr) children.push(sh) return function end () { while(children.length) children.shift().kill('SIGKILL') } } function try_often(times, opts, work, done) { if (typeof opts == 'function') { done = work work = opts opts = {} } const delay = 2000 setTimeout(function() { // delay first try console.log('try more:', times) work(function(err, result) { if (!err) return done(null, result) if (opts.ignore && err.message && !err.message.match(opts.ignore)) { console.error('Fatal error:', err) return done(err) } if (!times) return done(err) if(exited) return done(new Error('already exited')) console.warn('retry run', times) console.error('work(err):', err) try_often(times-1, work, done) }) }, delay) } function connect(port, host, cb) { var done = false var socket = net.connect(port, host) socket.on('error', function(err) { if (done) return done = true cb(err) }) socket.on('connect', function() { if (done) return done = true cb(null) }) } function testSsbServer(t, opts, asConfig, port, cb) { var dir = '/tmp/ssb-server_binjstest_' + Date.now() if('function' === typeof port) cb = port, port = opts.port mkdirp.sync(dir) var args = [ 'start', '--path '+dir ] if(asConfig) { fs.writeFileSync(join(dir, '.testrc'), JSON.stringify(opts)) } else { ;(function toArgs (prefix, opts) { for(var k in opts) { if(opts[k] && 'object' == typeof opts[k]) toArgs(prefix+k+'.', opts[k]) else args.push(prefix+k+'='+opts[k]) } })('--', opts) } var end = ssbServer(t, args, { cwd: dir }) try_often(10, { ignore: /ECONNREFUSED/ }, function work(cb) { connect(port, opts.host, cb) }, function (err) { cb(err) end() }) } var c = 0 ;['::1', '::', '127.0.0.1', 'localhost'].forEach(function (host) { if(!has_ipv6 && /:/.test(host)) return ;[9002, 9001].forEach(function (port) { ;[true, false].forEach(function (asConfig) { var opts = { host: host, port: 9001, ws: { port: 9002 } } // if(c++) return test('run bin.js server with ' + (asConfig ? 'a config file' : 'command line options') + ':'+JSON.stringify(opts)+' then connect to port:'+port , function(t) { testSsbServer(t, opts, true, function (err) { t.error(err, 'Successfully connect eventually') }) }) }) }) }) test('ssbServer should have websockets and http server by default', function(t) { var path = '/tmp/ssbServer_binjstest_' + Date.now() var caps = crypto.randomBytes(32).toString('base64') var end = ssbServer(t, [ 'start', '--host=127.0.0.1', '--port=9001', '--ws.port=9002', '--path', path, '--caps.shs', caps ]) try_often(10, function work(cb) { exec([ join(__dirname, '../bin.js'), 'getAddress', 'device', '--', '--host=127.0.0.1', '--port=9001', '--path', path, '--caps.shs', caps ].join(' '), { env: Object.assign({}, process.env, {ssb_appname: 'test'}) }, function(err, stdout, sderr) { if (err) return cb(err) cb(null, JSON.parse(stdout)) // remove quotes }) }, function(err, addr) { t.error(err, 'ssbServer getAdress succeeds eventually') if (err) return end() t.ok(addr, 'address is not null') t.comment('result of ssb-server getAddress: ' + addr) var remotes = ma.decode(addr) console.log('remotes', remotes, addr) ws_remotes = remotes.filter(function(a) { return a.find(function(component) { return component.name == 'ws' }) }) t.equal(ws_remotes.length, 1, 'has one ws remote') var remote = ma.encode([ws_remotes[0]]) // this breaks if multiserver address encoding changes t.ok(remote.indexOf('9002') > 0, 'ws address contains expected port') // this is a bit annoying. we can't signal ssb-client to load the secret from .path // it either has to be the first argument, already loaded var key = require('ssb-keys').loadOrCreateSync(join(path, 'secret')) require('ssb-client')(key, { path: path, caps: { shs: caps }, // has to be set when setting any config remote: remote }, function(err, ssb) { t.error(err, 'ssb-client returns no error') t.ok(ssb.manifest, 'got manifest from api') t.ok(ssb.version, 'got version from api') ssb.whoami(function(err, feed) { t.error(err, 'ssb.whoami succeeds') t.equal(feed.id[0], '@', 'feed.id has @ sigil') end() }) }) }) }) test('ssb-server client should work without options', function(t) { var path = '/tmp/ssb-server_binjstest_' + Date.now() mkdirp.sync(path) fs.writeFileSync(path+'/config', JSON.stringify({ port: 43293, ws: { port: 43294 } }) ) var caps = crypto.randomBytes(32).toString('base64') var end = ssbServer(t, [ 'start', '--path', path, '--config', path+'/config', '--caps.shs', caps ]) try_often(10, function work(cb) { exec([ join(__dirname, '../bin.js'), 'getAddress', 'device', '--path', path, '--config', path+'/config', '--caps.shs', caps ].join(' '), { env: Object.assign({}, process.env, {ssb_appname: 'test'}) }, function(err, stdout, sderr) { if (err) return cb(err) cb(null, JSON.parse(stdout)) // remove quotes }) }, function(err, addr) { t.error(err, 'ssb-server getAddress succeeds eventually') if (err) return end() t.ok(addr) t.comment('result of ssb-server getAddress: ' + addr) end() }) })