UNPKG

@huddly/device-api-usb

Version:

Huddly SDK device api which uses node-usb wrapper responsible for handling the transport layer of the communication and discovering the physical device/camera

726 lines 38.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = __importStar(require("chai")); const sinon_chai_1 = __importDefault(require("sinon-chai")); const chai_as_promised_1 = __importDefault(require("chai-as-promised")); const usb_1 = require("usb"); const transport_1 = __importDefault(require("./../src/transport")); const sinon_1 = __importDefault(require("sinon")); const messagepacket_1 = __importDefault(require("./../src/messagepacket")); const crypto_1 = __importDefault(require("crypto")); const endpoint_1 = require("usb/dist/usb/endpoint"); const rewire_1 = __importDefault(require("rewire")); chai_1.default.should(); chai_1.default.use(sinon_chai_1.default); chai_1.default.use(chai_as_promised_1.default); chai_1.default.use(require('chai-things')); const MAX_PACKET_SIZE = (16 * 1024); const createNewMockDevice = () => { return { serialNumber: 'B12344678', open: sinon_1.default.stub(), close: sinon_1.default.stub(), interfaces: [ { claim: sinon_1.default.stub(), release: sinon_1.default.stub(), descriptor: { bInterfaceClass: 255, }, endpoints: [ sinon_1.default.createStubInstance(endpoint_1.InEndpoint), sinon_1.default.createStubInstance(endpoint_1.OutEndpoint) ] } ] }; }; describe('UsbTransport', () => { const rewireModule = (0, rewire_1.default)('./../lib/src/transport'); const rewireClass = rewireModule.__get__('NodeUsbTransport'); const rewiredInstance = new rewireClass(); let transport; let mockedDevice; beforeEach(() => { mockedDevice = createNewMockDevice(); transport = new transport_1.default(mockedDevice); }); describe('#init', () => __awaiter(void 0, void 0, void 0, function* () { it('set in and out enpoints', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); (0, chai_1.expect)(transport.inEndpoint).to.be.an.instanceof(endpoint_1.InEndpoint); (0, chai_1.expect)(transport.outEndpoint).to.be.an.instanceof(endpoint_1.OutEndpoint); (0, chai_1.expect)(transport.vscInterface).not.be.undefined; (0, chai_1.expect)(transport.inEndpoint).not.be.undefined; (0, chai_1.expect)(transport.outEndpoint).not.be.undefined; })); it('should reject when no VSC interface is found on the device', () => __awaiter(void 0, void 0, void 0, function* () { const noVscInterfaceDevice = { open: () => { }, close: () => { }, interfaces: [{ descriptor: { bInterfaceClass: 25555555 } }] }; transport = new transport_1.default(noVscInterfaceDevice); return (0, chai_1.expect)(transport.init()).to.eventually.be.rejectedWith('No VSC Interface present on the usb device!'); })); it.skip('should close the device when claiming interface fails', () => { const spy = sinon_1.default.spy(transport_1.default.prototype, 'close'); const unclaimableInterfaceDevice = { open: () => { }, interfaces: [ { claim: sinon_1.default.stub().throws({ errno: usb_1.usb.LIBUSB_ERROR_BUSY, message: 'Cant claim it!' }), descriptor: { bInterfaceClass: 255 } } ] }; transport = new transport_1.default(unclaimableInterfaceDevice); return (0, chai_1.expect)(transport.init()).to.eventually.be.rejectedWith('Cant claim it!').then(function () { spy.should.have.been.called; }); }); it('should reject with proper error message when device is occupied by a different process', () => { const busyDevice = { open: sinon_1.default.stub().throws({ errno: usb_1.usb.LIBUSB_ERROR_ACCESS, message: 'Cant claim it!' }) }; transport = new transport_1.default(busyDevice); return (0, chai_1.expect)(transport.init()) .to.eventually.be.rejectedWith('Unable to claim usb interface. Please make sure the device is not used by another process!'); }); it('should reject if some other error occurs when claiming the interface', () => { const busyDevice = { open: sinon_1.default.stub().throws({ errno: usb_1.usb.LIBUSB_ERROR_OTHER, message: 'Cant open it!' }) }; transport = new transport_1.default(busyDevice); return (0, chai_1.expect)(transport.init()) .to.eventually.be.rejectedWith('Cant open it!'); }); it('should ignore redundant calls', () => __awaiter(void 0, void 0, void 0, function* () { for (let step = 0; step < 10; step++) { yield transport.init(); } (0, chai_1.expect)(mockedDevice.open).to.have.been.calledOnce; })); })); describe('#initEventLoop', () => { let startListenStub; const customMaxPcktSize = 1000; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); // @ts-ignore transport.inEndpoint.descriptor = { wMaxPacketSize: customMaxPcktSize }; startListenStub = sinon_1.default.stub(transport, 'startListen'); })); afterEach(() => { startListenStub.restore(); }); it('should throw error if InEndpoint is missing packet size info', () => { transport.inEndpoint.descriptor = undefined; const badFn = () => transport.initEventLoop(); (0, chai_1.expect)(badFn).throws('InEndpoint does not contain information about max packet size!'); }); it('should start the polling with maxPacketSize defined from wMaxPacketSize', () => { const stub = transport.inEndpoint.startPoll; transport.initEventLoop(); (0, chai_1.expect)(stub).to.have.been.calledOnce; (0, chai_1.expect)(stub.firstCall.args[0]).to.equal(1); (0, chai_1.expect)(stub.firstCall.args[1]).to.equal(customMaxPcktSize); (0, chai_1.expect)(startListenStub.callCount).to.equals(1); }); it('should ignore redundent calls', () => { const stub = transport.inEndpoint.startPoll; for (let step = 0; step < 10; step++) { transport.initEventLoop(); } (0, chai_1.expect)(stub).to.have.been.calledOnce; // Including the call from the previous test (0, chai_1.expect)(stub.firstCall.args[0]).to.equal(1); (0, chai_1.expect)(stub.firstCall.args[1]).to.equal(customMaxPcktSize); (0, chai_1.expect)(startListenStub.callCount).to.equals(1); }); }); describe('#performHlinkHandshake', () => { let outTransfer; let inTransfer; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); outTransfer = transport.outEndpoint.transfer; inTransfer = transport.inEndpoint.transfer; })); after(() => { outTransfer.restore(); inTransfer.restore(); }); it('should correctly perform hlink salutation process', () => __awaiter(void 0, void 0, void 0, function* () { outTransfer.callsFake((message, cb) => { cb(); }); inTransfer.callsFake((pkgSize, cb) => { cb(undefined, Buffer.from('HLink v0')); }); yield transport.performHlinkHandshake(); // Reset Sequence transfer (0, chai_1.expect)(outTransfer.callCount).to.be.equal(2); (0, chai_1.expect)(outTransfer.firstCall.args[0].compare(Buffer.alloc(0))).to.be.equal(0); (0, chai_1.expect)(outTransfer.secondCall.args[0].compare(Buffer.alloc(1, 0x00))).to.be.equal(0); // Read message (0, chai_1.expect)(inTransfer.callCount).to.be.equal(1); (0, chai_1.expect)(inTransfer.firstCall.args[0]).to.be.equal(1024); })); it('should fail when salutation message is not "Hlink v0"', () => __awaiter(void 0, void 0, void 0, function* () { outTransfer.callsFake((message, cb) => { cb(); }); inTransfer.callsFake((pkgSize, cb) => { cb(undefined, Buffer.from('N/A')); }); try { yield transport.performHlinkHandshake(); (0, chai_1.expect)(true).to.equals(false); } catch (error) { (0, chai_1.expect)(error).to.equal('Hlink handshake has failed! Wrong version. Expected HLink v0, got N/A.'); } })); }); describe('#readLoopReset', () => { it('should reset read loop helper variables', () => { rewiredInstance.readLoopChunks = [Buffer.from('hi')]; rewiredInstance.readLoopReset(); (0, chai_1.expect)(rewiredInstance.readLoopChunks.length).to.equal(0); (0, chai_1.expect)(rewiredInstance.currentBufferReadSize).to.equal(0); (0, chai_1.expect)(rewiredInstance.expectedReadBufferSize).to.equal(-1); (0, chai_1.expect)(rewiredInstance.currentStateOfReadLoop).to.equal(rewiredInstance.READ_STATES.NEW_READ); }); }); describe('#parseAndEmitFullyRetrievedMessage', () => { it('should parse and emit the encoded message received from device', () => { const encodedMsg = messagepacket_1.default.createMessage('hello-msg', Buffer.from('Greetings!')); const emitSpy = sinon_1.default.spy(); rewiredInstance.on('hello-msg', emitSpy); const resetLoopSpy = sinon_1.default.spy(rewiredInstance, 'readLoopReset'); rewiredInstance.readLoopChunks = [encodedMsg]; rewiredInstance.parseAndEmitFullyRetrievedMessage(); (0, chai_1.expect)(emitSpy).to.have.been.called; (0, chai_1.expect)(emitSpy.getCall(0).args[0]['message']).to.equal('hello-msg'); (0, chai_1.expect)(resetLoopSpy).to.have.been.called; }); }); describe('#continueReadLogic', () => { let parseStub; beforeEach(() => { parseStub = sinon_1.default.stub(rewiredInstance, 'parseAndEmitFullyRetrievedMessage'); }); afterEach(() => { parseStub.restore(); }); it('should update readloop state to pending when complete buffer is not read yet', () => { rewiredInstance.currentBufferReadSize = 1; rewiredInstance.expectedReadBufferSize = 2; rewiredInstance.currentStateOfReadLoop = undefined; rewiredInstance.continueReadLogic(); (0, chai_1.expect)(rewiredInstance.currentStateOfReadLoop).to.equal(rewiredInstance.READ_STATES.PENDING_CHUNK); }); it('should call #parseAndEmitFullyRetrievedMessage when buffer is fully received', () => { rewiredInstance.currentBufferReadSize = 2; rewiredInstance.expectedReadBufferSize = 2; rewiredInstance.currentStateOfReadLoop = rewiredInstance.READ_STATES.PENDING_CHUNK; rewiredInstance.continueReadLogic(); (0, chai_1.expect)(parseStub).to.have.been.called; }); }); describe('#onDataRetrievedHandler', () => { let continueLogicStub; beforeEach(() => { continueLogicStub = sinon_1.default.stub(rewiredInstance, 'continueReadLogic'); rewiredInstance.readLoopChunks = []; }); afterEach(() => { continueLogicStub.restore(); }); describe('on new read', () => { beforeEach(() => { rewiredInstance.currentStateOfReadLoop = rewiredInstance.READ_STATES.NEW_READ; }); describe('on "reset sequence" received', () => { it('should remove data event listener', () => { rewiredInstance.inEndpoint = { removeListener: sinon_1.default.stub() }; const stub = rewiredInstance.inEndpoint.removeListener; const badFn = () => rewiredInstance.onDataRetrievedHandler(Buffer.from('hi')); (0, chai_1.expect)(badFn).throws; try { badFn(); } catch (_a) { } // call the function to make sure the stub is invoked (0, chai_1.expect)(stub).to.have.been.calledOnce; (0, chai_1.expect)(stub.getCall(0).args[0]).to.equal('data'); }); it('should say that hlink reset seq received when buffer is an hlink reset seq', () => { const hlinkRes = Buffer.from('HLink v0'); const badFn = () => rewiredInstance.onDataRetrievedHandler(hlinkRes); (0, chai_1.expect)(badFn).throws('Received a hlink reset sequence. Read loop cannot continue!'); }); it('should throw error telling that an incomplete message was received', () => { const buff = Buffer.from('0000'); const badFn = () => rewiredInstance.onDataRetrievedHandler(buff); (0, chai_1.expect)(badFn).throws(`Received an incomplete message on start of read! Message size: ${buff.length}. Unable to proceed, exiting ungracefully!`); }); }); it('should parse buffer, populate read loop chunk and set the current read size', () => { const encodedMsg = messagepacket_1.default.createMessage('hello-msg', Buffer.from('Greetings!')); rewiredInstance.onDataRetrievedHandler(encodedMsg); (0, chai_1.expect)(rewiredInstance.expectedReadBufferSize).to.equals(35); (0, chai_1.expect)(rewiredInstance.readLoopChunks).to.deep.equal([encodedMsg]); (0, chai_1.expect)(rewiredInstance.currentBufferReadSize).to.equal(35); }); }); describe('on pending read', () => { const buffersToProcess = [ Buffer.from('hi'), Buffer.from('you!') ]; beforeEach(() => { rewiredInstance.currentStateOfReadLoop = rewiredInstance.READ_STATES.PENDING_CHUNK; rewiredInstance.readLoopChunks = [buffersToProcess[0]]; rewiredInstance.currentBufferReadSize = buffersToProcess[0].length; }); it('should append new buffer to the read loop chunks and update current read size', () => { rewiredInstance.onDataRetrievedHandler(buffersToProcess[1]); (0, chai_1.expect)(rewiredInstance.readLoopChunks.length).to.equal(buffersToProcess.length); (0, chai_1.expect)(rewiredInstance.readLoopChunks).to.deep.equal(buffersToProcess); (0, chai_1.expect)(rewiredInstance.currentBufferReadSize).to.equal(buffersToProcess[0].length + buffersToProcess[1].length); }); }); }); describe('#startListen', () => { it('should process incoming data and emit result', () => __awaiter(void 0, void 0, void 0, function* () { const encodedMsg = messagepacket_1.default.createMessage('hello-msg', Buffer.from('Greetings!')); yield transport.init(); const inEndpointStub = transport.inEndpoint.on; inEndpointStub.withArgs('data') .onCall(0).callsFake((msg, cb) => cb(encodedMsg)); const emitSpy = sinon_1.default.spy(transport, 'emit'); transport.startListen(); (0, chai_1.expect)(emitSpy.callCount).to.equals(1); (0, chai_1.expect)(emitSpy.firstCall.args[0]).to.equals('hello-msg'); (0, chai_1.expect)(emitSpy.firstCall.args[1]).to.deep.equals(messagepacket_1.default.parseMessage(encodedMsg)); })); it('should process buffer chunks when not received as a whole'); it('should call #close on error message', () => __awaiter(void 0, void 0, void 0, function* () { const spy = sinon_1.default.spy(transport, 'close'); yield transport.init(); const onDataStub = transport.inEndpoint.once; onDataStub.withArgs('error').callsFake((msg, cb) => cb('Error -1')); transport.startListen(); (0, chai_1.expect)(spy).to.have.been.calledOnce; })); it('should remove data listener on close event', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); const removeListenerStub = transport.inEndpoint.removeListener; transport.startListen(); transport.emit('CLOSED'); (0, chai_1.expect)(removeListenerStub).to.have.been.calledOnce; (0, chai_1.expect)(removeListenerStub.getCall(0).args[0]).to.equal('data'); })); it('should call #close on transport error event', () => __awaiter(void 0, void 0, void 0, function* () { const spy = sinon_1.default.spy(transport, 'close'); yield transport.init(); transport.startListen(); transport.emit('ERROR', { errno: usb_1.usb.LIBUSB_ERROR_INTERRUPTED }); (0, chai_1.expect)(spy).to.have.been.calledOnce; })); }); describe('#on', () => { it('should return an event emitter', () => { const on = transport.on('message', () => { }); (0, chai_1.expect)(on).to.be.instanceof(transport_1.default); }); }); describe('#removeListener', () => { it('should return an event emitter', () => { const on = transport.removeListener('message', () => { }); (0, chai_1.expect)(on).to.be.instanceof(transport_1.default); }); }); describe('#removeAllListeners', () => { it('should return an event emitter', () => { const on = transport.removeAllListeners('message'); (0, chai_1.expect)(on).to.be.instanceof(transport_1.default); }); }); describe('#receiveMessage', () => { let onStub; let fakeTimer; beforeEach(() => { onStub = sinon_1.default.stub(transport, 'once'); fakeTimer = sinon_1.default.useFakeTimers(); }); afterEach(() => { onStub.restore(); fakeTimer.restore(); }); describe('on success', () => { it('should resolve `once` the message is emitted from super class', () => __awaiter(void 0, void 0, void 0, function* () { const msg = { name: 'hello', payload: 'hello_back' }; onStub.callsFake((message, cb) => { cb(msg); }); const t = yield transport.receiveMessage('hello', 500); fakeTimer.tick(510); (0, chai_1.expect)(t).to.deep.equals(msg); })); it('should remove error message listener when main message is emitted from super class', () => __awaiter(void 0, void 0, void 0, function* () { const removeListenerSpy = sinon_1.default.spy(transport, 'removeListener'); const msg = { name: 'test', payload: 'test 123' }; onStub.callsFake((message, cb) => { cb(msg); }); const t = yield transport.receiveMessage('test', 500); fakeTimer.tick(510); (0, chai_1.expect)(t).to.deep.equals(msg); (0, chai_1.expect)(removeListenerSpy.callCount).to.equals(2); (0, chai_1.expect)(removeListenerSpy.getCall(0).args[0]).to.equals('ERROR'); (0, chai_1.expect)(removeListenerSpy.getCall(1).args[0]).to.equals(msg.name); })); }); describe('on timeout', () => { it('should reject with timeout error message when timeout exceeded waiting for message to be emitted', () => __awaiter(void 0, void 0, void 0, function* () { const spy = sinon_1.default.spy(transport, 'removeAllListeners'); try { const p = transport.receiveMessage('timeout_msg', 10); fakeTimer.tick(100); yield p; } catch (e) { (0, chai_1.expect)(transport.removeAllListeners).to.have.been.calledWith('timeout_msg'); (0, chai_1.expect)(e).to.equals('Request has timed out! timeout_msg 10'); (0, chai_1.expect)(spy.calledOnce).to.equals(true); return; } throw new Error('Did not reject receiveMessage when msg receiver times out!'); })); }); describe('on error', () => { it('should reject with error message and clear listener on main message', () => __awaiter(void 0, void 0, void 0, function* () { const removeListenerSpy = sinon_1.default.spy(transport, 'removeListener'); onStub.withArgs('ERROR').onCall(0).callsFake((msg, cb) => cb('Error Occurred!')); try { const p = transport.receiveMessage('buggy_msg', 10); fakeTimer.tick(100); yield p; } catch (e) { (0, chai_1.expect)(e).to.equals('Error Occurred!'); (0, chai_1.expect)(removeListenerSpy.callCount).to.equals(1); (0, chai_1.expect)(removeListenerSpy.getCall(0).args[0]).to.equals('buggy_msg'); return; } throw new Error('Did not reject receiveMessage when camera emits ERROR!'); })); }); it('should resolve when message comes through, other listeners should stay intact', () => __awaiter(void 0, void 0, void 0, function* () { const msg = { name: 'hello', payload: 'hello_back' }; const messageSpy = sinon_1.default.spy(); onStub.callsFake((message, cb) => { cb(msg); }); transport.on('test-subscribe', messageSpy); yield transport.receiveMessage('test-subscribe', 500); fakeTimer.tick(510); transport.emit('test-subscribe'); (0, chai_1.expect)(messageSpy).to.have.callCount(1); })); }); describe('#write', () => { let transferStub; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { transferStub = sinon_1.default.stub(transport, 'sendChunk'); transferStub.returns(Promise.resolve()); yield transport.init(); })); afterEach(() => { transferStub.restore(); }); it('should package the message command and its payload and call transfer', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.write('echo-test', Buffer.alloc(0)); (0, chai_1.expect)(transferStub).to.have.calledWith(messagepacket_1.default.createMessage('echo-test', Buffer.alloc(0))); })); describe('on LIBUSB_ERROR_IO', () => { let splitStub; beforeEach(() => { splitStub = sinon_1.default.stub(transport, 'splitAndSendPayloadInChunks'); splitStub.resolves(); }); afterEach(() => { splitStub.restore(); }); it('should fallback to splitting data in chunks', () => __awaiter(void 0, void 0, void 0, function* () { transferStub.rejects(new Error('LIBUSB_ERROR_IO')); yield transport.write('echo-test', Buffer.alloc(16 * 1024 + 1)); (0, chai_1.expect)(splitStub).to.have.been.calledOnce; })); it('should reject if even the fallback method fails with some other error', () => { transferStub.rejects(new Error('LIBUSB_ERROR_IO')); splitStub.rejects(new Error('LIBUSB_ERROR_OTHER')); return (0, chai_1.expect)(transport.write('echo-test', Buffer.alloc(16 * 1024 + 1))) .to.eventually.be.rejectedWith('LIBUSB_ERROR_OTHER'); }); it('should reject if size of buffer is still below MAX_PACKET_SIZE', () => { transferStub.rejects(new Error('LIBUSB_ERROR_IO')); return (0, chai_1.expect)(transport.write('echo-test', Buffer.alloc(1))).to.eventually.be.rejectedWith('LIBUSB_ERROR_IO'); }); }); describe('on other error', () => { it('should reject with original error', () => { transferStub.rejects(new Error('LIBUSB_ERROR_OTHER')); return (0, chai_1.expect)(transport.write('echo-test', Buffer.alloc(1))).to.eventually.be.rejectedWith('LIBUSB_ERROR_OTHER'); }); }); describe('#subscribe', () => { it('should send a hlink subscribe message', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.subscribe('test-subscribe'); (0, chai_1.expect)(transferStub).to.have.calledWith(messagepacket_1.default.createMessage('hlink-mb-subscribe', 'test-subscribe')); })); }); describe('#unsubscribe', () => { it('should send a hlink unsubscribe message', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.unsubscribe('test-unsubscribe'); (0, chai_1.expect)(transferStub).to.have.calledWith(messagepacket_1.default.createMessage('hlink-mb-unsubscribe', 'test-unsubscribe')); })); }); }); describe('#splitAndSendPayloadInChunks', () => { let sendChunkStub; beforeEach(() => { sendChunkStub = sinon_1.default.stub(transport_1.default.prototype, 'sendChunk'); }); afterEach(() => sendChunkStub.restore()); it('should transfer buffer in chunks', () => __awaiter(void 0, void 0, void 0, function* () { const command = 'ECHO'; const payloadLen = (MAX_PACKET_SIZE - command.length - 16) * 2; const payload = crypto_1.default.randomBytes(payloadLen); const msg = messagepacket_1.default.createMessage(command, payload); yield transport.init(); yield transport.splitAndSendPayloadInChunks(msg); (0, chai_1.expect)(sendChunkStub.callCount).to.be.equal(2); (0, chai_1.expect)(sendChunkStub.firstCall.args[0].length).to.equal(MAX_PACKET_SIZE); })); }); describe('#readChunk', () => { let inEndpointStub; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); inEndpointStub = transport.inEndpoint.transfer; })); afterEach(() => { inEndpointStub.restore(); }); it('should call transfer on out endpoint', () => __awaiter(void 0, void 0, void 0, function* () { inEndpointStub.callsFake((packetsize, cb) => { cb(); }); yield transport.readChunk(MAX_PACKET_SIZE); (0, chai_1.expect)(inEndpointStub).to.been.called; (0, chai_1.expect)(inEndpointStub.firstCall.args[0]).to.be.equal(MAX_PACKET_SIZE); })); it('should reject when transfer fails', () => { inEndpointStub.callsFake((packetsize, cb) => { cb({ errno: usb_1.usb.LIBUSB_ERROR_OVERFLOW, message: 'OVERFLOW!' }); }); return (0, chai_1.expect)(transport.readChunk(1)) .to.eventually.be.rejectedWith(`Unable to read data from device (LibUSBException: ${usb_1.usb.LIBUSB_ERROR_OVERFLOW})! OVERFLOW`); }); }); describe('#sendChunk', () => { let outEndpointStub; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); outEndpointStub = transport.outEndpoint.transfer; })); afterEach(() => { outEndpointStub.restore(); }); it('should call transfer on in endpoint', () => __awaiter(void 0, void 0, void 0, function* () { outEndpointStub.callsFake((message, cb) => { cb(); }); const bufferToSend = Buffer.from('send-me'); yield transport.sendChunk(bufferToSend); (0, chai_1.expect)(outEndpointStub).to.been.called; (0, chai_1.expect)(outEndpointStub.firstCall.args[0]).to.be.equal(bufferToSend); })); it('should reject when transfer fails', () => { outEndpointStub.callsFake((packetsize, cb) => { cb({ errno: usb_1.usb.LIBUSB_ERROR_OVERFLOW, message: 'OVERFLOW!' }); }); return (0, chai_1.expect)(transport.sendChunk(Buffer.from('send-me'))) .to.eventually.be.rejectedWith(`Unable to write data to device (LibUSBException: ${usb_1.usb.LIBUSB_ERROR_OVERFLOW})! OVERFLOW`); }); }); describe('#stopUsbEndpointPoll', () => { it('should call stop poll on inEndpoint', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); const stopPolStub = transport.inEndpoint.stopPoll; stopPolStub.callsFake((cb) => { cb(); }); return (0, chai_1.expect)(transport.stopUsbEndpointPoll()).to.eventually.be.fulfilled; })); it('should reject if inendpoint emits error before polling is stopped'); it('should ignore LIBUSB_TRANSFER_NO_DEVICE since this happens on device detach'); }); describe('#stopEventLoop', () => { let removeListenerStub; let stopPoll; beforeEach(() => { removeListenerStub = sinon_1.default.stub(transport_1.default.prototype, 'removeAllListeners'); stopPoll = sinon_1.default.stub(transport_1.default.prototype, 'stopUsbEndpointPoll'); stopPoll.resolves(); }); afterEach(() => { removeListenerStub.restore(); stopPoll.restore(); }); it('should call remove all listeners on super class', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.stopEventLoop(); (0, chai_1.expect)(removeListenerStub).to.have.been.called; })); describe('stop_poll', () => { let startPollStub; let startListenStub; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); startListenStub = sinon_1.default.stub(transport_1.default.prototype, 'startListen'); startPollStub = transport.inEndpoint.startPoll; startPollStub.returns(true); // @ts-ignore transport.inEndpoint.descriptor = { wMaxPacketSize: 1024 }; })); afterEach(() => { startListenStub.restore(); startPollStub.restore(); }); it('should stop usb endpoint poll if active', () => __awaiter(void 0, void 0, void 0, function* () { transport.initEventLoop(); yield transport.stopEventLoop(); (0, chai_1.expect)(stopPoll).to.have.been.called; })); it('should reject if #stopUsbEndpointPoll fails', () => __awaiter(void 0, void 0, void 0, function* () { transport.initEventLoop(); stopPoll.rejects(new Error('Cant do that!')); return (0, chai_1.expect)(transport.stopEventLoop()).to.eventually.be.rejectedWith('Cant do that!'); })); }); }); describe('#releaseEndpoints', () => { let stopPollStub; let releaseStub; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); stopPollStub = sinon_1.default.stub(transport_1.default.prototype, 'stopUsbEndpointPoll'); stopPollStub.resolves(); releaseStub = transport.vscInterface.release; })); afterEach(() => { stopPollStub.restore(); }); it('should stop endpoint poll and release the vsc interface', () => __awaiter(void 0, void 0, void 0, function* () { releaseStub.yields(undefined); yield transport.releaseEndpoints(); (0, chai_1.expect)(stopPollStub).to.have.been.called; (0, chai_1.expect)(releaseStub).to.have.been.called; })); it('should reject if stopping endpoint poll fails', () => { stopPollStub.rejects(new Error('Uuups, problem!')); return (0, chai_1.expect)(transport.releaseEndpoints()).to.eventually.be.rejectedWith('Uuups, problem!'); }); it('should ignore LIBUSB_ERROR_NO_DEVICE when releasing endpoint fails', () => { releaseStub.yields({ errno: usb_1.usb.LIBUSB_ERROR_NO_DEVICE }); return (0, chai_1.expect)(transport.releaseEndpoints()).to.eventually.be.fulfilled; }); it('should reject for any other libusb errors thrown on endpoint release', () => { releaseStub.yields({ errno: usb_1.usb.LIBUSB_ERROR_ACCESS, message: 'Device is busy!' }); return (0, chai_1.expect)(transport.releaseEndpoints()).to.eventually.be.rejectedWith('Unable to release vsc interface! UsbError: -3 \nDevice is busy!'); }); }); describe('#close', () => { let releaseEndpointStub; beforeEach(() => __awaiter(void 0, void 0, void 0, function* () { yield transport.init(); releaseEndpointStub = sinon_1.default.stub(transport_1.default.prototype, 'releaseEndpoints'); releaseEndpointStub.resolves(); })); afterEach(() => { releaseEndpointStub.restore(); }); it('should release endpoints', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.close(); (0, chai_1.expect)(releaseEndpointStub).to.have.been.calledOnce; })); it('should attempt to close device', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.close(); (0, chai_1.expect)(mockedDevice.close).to.have.been.calledOnce; })); it('should emit closed event', () => __awaiter(void 0, void 0, void 0, function* () { const spy = sinon_1.default.spy(); transport.on('CLOSED', spy); yield transport.close(); (0, chai_1.expect)(spy).to.have.been.calledOnce; })); it('should assume closed regardless if close fails', () => { // Tihs is the case where we want to close a device that is detached/booted mockedDevice.close.throws(new Error('Pending transfers......')); return (0, chai_1.expect)(transport.close()).to.eventually.be.fulfilled; }); it('should reject if releasing endpoints fails', () => { releaseEndpointStub.rejects(new Error('Undefined')); return (0, chai_1.expect)(transport.close()).to.eventually.be.rejectedWith('Undefined'); }); }); describe('#deprecated', () => { describe('#receive', () => { it('should not be supported', () => (0, chai_1.expect)(transport.receive).to.throw('Method "receive" is no longer supported! Please use "receiveMessage" instead.')); }); describe('#read', () => { it('should not be supported', () => (0, chai_1.expect)(transport.read).to.throw('Method "read" is no longer supported! Please use "receiveMessage" instead.')); }); }); describe('#clear', () => { it('should resolve', () => __awaiter(void 0, void 0, void 0, function* () { yield transport.clear(); (0, chai_1.expect)(true).to.equal(true); })); }); }); //# sourceMappingURL=transport.spec.js.map