UNPKG

@m-lab/ndt7

Version:

NDT7 client for measuring networks

414 lines (395 loc) 12 kB
/* eslint-disable require-jsdoc */ /* eslint-env es6, node, browser, mocha */ 'use strict'; const ndt7 = require('../ndt7'); const chai = require('chai'); /* function webSocketMocks() { const vars = { instance: undefined, out: [], realURL: undefined, realProtocol: undefined, }; vars.newWebSocket = function(url, protocol) { if (vars.instance !== undefined) { throw new Error('too many new WebSocket calls'); } vars.realURL = url; vars.realProtocol = protocol; this.onclose = undefined; this.onopen = undefined; this.onmessage = undefined; this.close = function() { this.onclose(); }; this.bufferedAmount = 0; this.send = function(msg) { vars.out.push(msg); }; vars.instance = this; }; vars.open = function() { vars.instance.onopen(); }; vars.send = function(msg) { vars.instance.onmessage(msg); }; return vars; } function blobMocks(array) { array.size = array.length; } function dateMocks() { const times = []; return { newDate: function() { this.getTime = function() { return times.pop(); }; }, pushTime: function(now) { times.push(now); }, size: function() { return times.length; }, }; } */ /** * postMessageMocks contains mocks for postMessage * @return {Object} An object with a postMessage function. */ /* function postMessageMocks() { const messages = []; return { postMessage: function(msg) { messages.push(msg); }, pop: function() { return messages.pop(); }, size: function() { return messages.length; }, }; } */ describe('ndt7.noop', function() { it('this is a placeholder for working unit tests', function(done) { chai.assert(typeof(ndt7) !== 'undefined'); chai.assert(true); done(); }); }); /* describe('ndt7.DownloadTest', function() { it('should work as intended', function(done) { // create the mocks const datemocks = dateMocks(); const postmessagemocks = postMessageMocks(); const wsmocks = webSocketMocks(); // start download ndt7.startDownload({ Blob: blobMocks, Date: datemocks.newDate, WebSocket: wsmocks.newWebSocket, baseURL: 'https://www.example.com/', postMessage: postmessagemocks.postMessage, }); // we open the connection datemocks.pushTime(10); wsmocks.open(); if (datemocks.size() !== 0) { throw new Error('the code did not get the test start time'); } chai.assert(wsmocks.realURL === 'wss://www.example.com/ndt/v7/download'); chai.assert(wsmocks.realProtocol === 'net.measurementlab.ndt.v7'); // we receive a binary messages datemocks.pushTime(100); wsmocks.send({ data: new BlobMocks(new Uint8Array(4096)), }); if (postmessagemocks.size() !== 0) { throw new Error('the code did fire postMessage too early'); } if (datemocks.size() !== 0) { throw new Error('the code did not get the current time'); } // we receive a server measurement datemocks.pushTime(150); wsmocks.send({ data: `{"TCPInfo": {"RTT": 100}}`, }); if (postmessagemocks.size() !== 1) { throw new Error('unexpected number of posted messages'); } chai.assert.equal(postmessagemocks.pop(), { Origin: 'server', TCPInfo: { RTT: 100, }, Test: 'download', }); if (datemocks.size() !== 0) { throw new Error('the code did not get the current time'); } // we receive more data after more than 250 ms from previous beginning datemocks.pushTime(350); wsmocks.send({ data: new BlobMocks(new Uint8Array(4096)), }); if (postmessagemocks.size() !== 1) { throw new Error('unexpected number of posted messages'); } chai.assert.equal(postmessagemocks.pop(), { AppInfo: { ElapsedTime: 340000, NumBytes: 8217, }, Origin: 'client', Test: 'download', }); if (datemocks.size() !== 0) { throw new Error('the code did not get the current time'); } done(); }); }); describe('ndt7.startUpload', function() { it('should work as intended', function(done) { // create the mocks const datemocks = dateMocks(); const wsmocks = webSocketMocks(); let lastNumBytes = 0; let lastElapsedTime = 0; // start download ndt7.startUpload({ Date: datemocks.newDate, WebSocket: wsmocks.newWebSocket, baseURL: 'https://www.example.com/', postMessage: function(ev) { if (ev === null) { if (wsmocks.out.length != 22) { throw new Error('unexpected number of queued messages'); } if (wsmocks.out[17].length != 16384) { throw new Error('messages were not scaled'); } done(); return; } if (ev.Origin !== 'client') { throw new Error('unexpected message Origin'); } if (ev.Test !== 'upload') { throw new Error('unexpected message Test'); } if (ev.AppInfo.NumBytes <= lastNumBytes) { throw new Error('NumBytes didn\'t increment'); } if (ev.AppInfo.ElapsedTime <= lastElapsedTime) { throw new Error('ElapsedTime didn\'t increment'); } lastElapsedTime = ev.AppInfo.ElapsedTime; lastNumBytes = ev.AppInfo.NumBytes; }, }); // we open the connection for (let t = 11000; t > 0; t -= 440) { // reverse order since we push datemocks.pushTime(t); } wsmocks.open(); chai.assert(wsmocks.realURL === 'wss://www.example.com/ndt/v7/upload'); chai.assert(wsmocks.realProtocol === 'net.measurementlab.ndt.v7'); }); }); function makeMocks(config) { const state = { workerMain: config.workerMain, workerTerminated: 0, }; state.Worker = function() { this.onerror = undefined; this.onmessage = undefined; this.terminate = function() { state.workerTerminated++; }; this.postMessage = function(ev) { state.workerMain(this, ev); }; }; return state; } describe('ndt7.start', function() { it('should throw if there is no config', function() { chai.assert.throws(function() { ndt7.start(); }, Error); }); it('should throw if there is no userAcceptDataPolicy field', function() { chai.assert.throws(function() { ndt7.start({}); }, Error); }); it('should throw if user did not accept data policy', function() { chai.assert.throws(function() { ndt7.start({userAcceptedDataPolicy: false}); }, Error, 'fatal: user must accept data policy first'); }); it('should work as intended without optional callbacks', function(done) { const expectedURL = 'https://www.example.com/'; ndt7.start({ doStartWorker: function(config, url, name, callback) { chai.assert(typeof config === 'object'); chai.assert(url === expectedURL); chai.assert(name === 'download' || name === 'upload'); callback(); }, locate: function(callback) { callback(expectedURL); }, oncomplete: done, onstarting: function() {}, userAcceptedDataPolicy: true, }); }); it('should work as intended with optional callbacks', function(done) { let calledOnStarting = false; let calledOnServerURL = false; const expectedURL = 'https://www.example.com/'; ndt7.start({ doStartWorker: function(config, url, name, callback) { chai.assert(typeof config === 'object'); chai.assert(url === expectedURL); chai.assert(name === 'download' || name === 'upload'); chai.assert(calledOnServerURL == true); chai.assert(calledOnStarting == true); callback(); }, locate: function(callback) { callback(expectedURL); }, oncomplete: done, onstarting: function() { calledOnStarting = true; }, onserverurl: function(url) { calledOnServerURL = true; chai.assert(url === expectedURL); }, userAcceptedDataPolicy: true, }); }); it('should behave correctly when the worker is killed', function(done) { const complete = []; const mocks = makeMocks({ workerMain: function() {}, }); ndt7.start({ Worker: mocks.Worker, killAfter: 7, locate: function(callback) { callback('https://www.example.com/'); }, oncomplete: function() { chai.assert(mocks.workerTerminated === 2); chai.assert(complete.length === 2); chai.assert(complete[0].Origin === 'client'); chai.assert(complete[0].Test === 'download'); chai.assert(complete[0].WorkerInfo.ElapsedTime > 0); chai.assert(complete[0].WorkerInfo.Error === 'Terminated with timeout'); chai.assert(complete[1].Origin === 'client'); chai.assert(complete[1].Test === 'upload'); chai.assert(complete[1].WorkerInfo.ElapsedTime > 0); chai.assert(complete[1].WorkerInfo.Error === 'Terminated with timeout'); done(); }, ontestcomplete: function(ev) { complete.push(ev); }, userAcceptedDataPolicy: true, }); }); it('should behave correctly when the worker throws', function(done) { const complete = []; const mocks = makeMocks({ workerMain: function(worker) { setTimeout(function() { worker.onerror({}); }, 10); }, }); ndt7.start({ Worker: mocks.Worker, locate: function(callback) { callback('https://www.example.com/'); }, oncomplete: function() { chai.assert(complete.length === 2); chai.assert(complete[0].Origin === 'client'); chai.assert(complete[0].Test === 'download'); chai.assert(complete[0].WorkerInfo.ElapsedTime > 0); chai.assert( complete[0].WorkerInfo.Error === 'Terminated with exception'); chai.assert(complete[1].Origin === 'client'); chai.assert(complete[1].Test === 'upload'); chai.assert(complete[1].WorkerInfo.ElapsedTime > 0); chai.assert( complete[1].WorkerInfo.Error === 'Terminated with exception'); done(); }, ontestcomplete: function(ev) { complete.push(ev); }, userAcceptedDataPolicy: true, }); }); it( 'should behave correctly when the worker works as intended', function(done) { const complete = []; const mocks = makeMocks({ workerMain: function(worker, ev) { chai.assert(ev.baseURL === 'https://www.example.com/'); setTimeout(function() { worker.onmessage({data: {antani: 1}}); worker.onmessage({data: null}); }, 7); }, }); const msgs = []; ndt7.start({ Worker: mocks.Worker, locate: function(callback) { callback('https://www.example.com/'); }, oncomplete: function() { chai.assert(msgs.length === 2); chai.assert(msgs[0].antani === 1); chai.assert(msgs[1].antani === 1); chai.assert(complete.length === 2); chai.assert(complete[0].Origin === 'client'); chai.assert(complete[0].Test === 'download'); chai.assert(complete[0].WorkerInfo.ElapsedTime > 0); chai.assert(complete[0].WorkerInfo.Error === null); chai.assert(complete[1].Origin === 'client'); chai.assert(complete[1].Test === 'upload'); chai.assert(complete[1].WorkerInfo.ElapsedTime > 0); chai.assert(complete[1].WorkerInfo.Error === null); done(); }, ontestcomplete: function(ev) { complete.push(ev); }, ontestmeasurement: function(ev) { msgs.push(ev); }, userAcceptedDataPolicy: true, }); }, ); }); */