diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..394522f4 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +node_modules/** \ No newline at end of file diff --git a/test/global.js b/test/global.js new file mode 100644 index 00000000..4d5126d3 --- /dev/null +++ b/test/global.js @@ -0,0 +1,51 @@ +const ws_api = require('ws'); + +module.exports = { + init: function(endpoint) { + wsurl = endpoint; + }, + testcontext: function (methodname) { + + let ws = new ws_api(wsurl, { rejectUnauthorized: false }); + + let timer = setTimeout(() => { + ws.removeAllListeners(); + ws.close(); + fail(methodname, "Timeout"); + }, 1000); + + let ctx = { + ws, + pass: function (message) { + clearTimeout(timer); + ws.removeAllListeners(); + ws.close(); + pass(methodname, message); + }, + fail: function (message) { + clearTimeout(timer); + ws.removeAllListeners(); + ws.close(); + fail(methodname, message); + } + } + + ws.onerror = function () { + ctx.fail('Connection error'); + }; + + return ctx; + } +} + +let wsurl = ''; + +function pass(methodname, message) { + let log = "PASS: " + methodname + (message ? ": " + message : ""); + console.log('\x1b[32m%s\x1b[0m', log) +} + +function fail(methodname, message) { + let log = "FAIL: " + methodname + (message ? ": " + message : ""); + console.log('\x1b[31m%s\x1b[0m', log); +} \ No newline at end of file diff --git a/test/package-lock.json b/test/package-lock.json new file mode 100644 index 00000000..558a2792 --- /dev/null +++ b/test/package-lock.json @@ -0,0 +1,32 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "libsodium": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.5.tgz", + "integrity": "sha512-0YVU2QJc5sDR5HHkGCaliYImS7pGeXi11fiOfm4DirBd96PJVZIn3LJa06ZOFjLNsWkL3UbNjYhLRUOABPL9vw==" + }, + "libsodium-wrappers": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.5.tgz", + "integrity": "sha512-QE9Q+FxLLGdJRiJTuC2GB3LEHZeHX/VcbMQeNPdAixEKo86JPy6bOWND1XmMLu0tjWUu0xIY0YpJYQApxIZwbQ==", + "requires": { + "libsodium": "0.7.5" + } + }, + "ws": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz", + "integrity": "sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==", + "requires": { + "async-limiter": "^1.0.0" + } + } + } +} diff --git a/test/package.json b/test/package.json new file mode 100644 index 00000000..83643285 --- /dev/null +++ b/test/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "libsodium-wrappers": "0.7.5", + "ws": "7.1.2" + } +} diff --git a/test/test.js b/test/test.js new file mode 100644 index 00000000..ad02f24d --- /dev/null +++ b/test/test.js @@ -0,0 +1,9 @@ +const global = require('./global') +const challenge_tests = require('./test_challenge') + +function runtests() { + challenge_tests.run(); +} + +global.init('wss://localhost:8081'); +runtests(); \ No newline at end of file diff --git a/test/test_challenge.js b/test/test_challenge.js new file mode 100644 index 00000000..bf0aefbf --- /dev/null +++ b/test/test_challenge.js @@ -0,0 +1,90 @@ +const sodium = require('libsodium-wrappers') +const global = require('./global') + +module.exports = { + run: function () { + sodium.ready.then(() => + testmethods.forEach(f => f())); + } +} + +let testmethods = [ + () => { + + let ctx = global.testcontext("challenge: format_is_correct"); + + ctx.ws.on('message', (m) => { + + let obj = {}; + try { + obj = JSON.parse(m); + } catch { + ctx.fail('JSON parse failed'); + return; + } + + if (obj.version && obj.type == 'public_challenge' && obj.challenge) + ctx.pass(); + else + ctx.fail('Invalid fields'); + }); + }, + + () => { + + let ctx = global.testcontext("challenge: accepts_connection_for_valid_signature"); + + ctx.ws.on('message', (m) => { + let obj = JSON.parse(m); + var keys = sodium.crypto_sign_keypair(); + + // sign the challenge and send back the response + var sigbytes = sodium.crypto_sign_detached(obj.challenge, keys.privateKey); + ctx.ws.send(JSON.stringify({ + type: 'challenge_resp', + challenge: obj.challenge, + sig: Buffer.from(sigbytes).toString('hex'), + pubkey: 'ed' + Buffer.from(keys.publicKey).toString('hex') + })); + + let timer = setTimeout(() => { + // Wait 500ms and pass. That means we haven't been disconnected thus far. + ctx.pass(); + }, 500); + + ctx.ws.on('close', () => { + // Clear the pass timer and fail test if we get disconnected. + clearTimeout(timer); + ctx.fail('Got disconnected'); + }); + }); + }, + + () => { + + let ctx = global.testcontext("challenge: rejects_connection_for_empty_response"); + + ctx.ws.on('message', (m) => { + + ctx.ws.send(JSON.stringify({})) + + ctx.ws.on('close', () => { + ctx.pass(); + }) + }); + }, + + () => { + + let ctx = global.testcontext("challenge: rejects_connection_for_invalid_type"); + + ctx.ws.on('message', (m) => { + + ctx.ws.send(JSON.stringify({ type: 'dummy_type' })) + + ctx.ws.on('close', () => { + ctx.pass(); + }) + }); + } +] \ No newline at end of file