@abandonware/noble
Version:
A Node.js BLE (Bluetooth Low Energy) central library.
457 lines (379 loc) • 15.8 kB
JavaScript
const sinon = require('sinon');
const should = require('should');
const proxyquire = require('proxyquire').noCallThru();
const { assert } = sinon;
describe('webbluetooth bindings', () => {
const FakeWebsocket = sinon.spy();
const Bindings = proxyquire('../../../lib/websocket/bindings', {
ws: FakeWebsocket
});
let bindings;
beforeEach(() => {
FakeWebsocket.prototype.on = sinon.spy();
bindings = new Bindings();
});
afterEach(() => {
sinon.reset();
});
it('constructor', () => {
assert.calledOnceWithExactly(FakeWebsocket, 'ws://localhost:2846');
assert.callCount(bindings._ws.on, 4);
assert.calledWithMatch(bindings._ws.on, 'open', sinon.match.func);
assert.calledWithMatch(bindings._ws.on, 'close', sinon.match.func);
assert.calledWithMatch(bindings._ws.on, 'error', sinon.match.func);
assert.calledWithMatch(bindings._ws.on, 'message', sinon.match.func);
});
it('onClose - should emit stateChange', () => {
const callback = sinon.spy();
bindings.on('stateChange', callback);
bindings._onClose();
assert.calledOnceWithExactly(callback, 'poweredOff');
});
describe('_onMessage', () => {
const mainEvent = {
peripheralUuid: 'peripheralUuid',
address: 'address',
addressType: 'addressType',
connectable: 'connectable',
advertisement: {
localName: 'localName',
txPowerLevel: 34,
serviceUuids: 'ad.serviceUuids',
manufacturerData: null,
serviceData: null
},
rssi: 'rssi',
serviceUuids: 'serviceUuids',
serviceUuid: 'serviceUuid',
includedServiceUuids: 'includedServiceUuids',
characteristics: 'characteristics',
characteristicUuid: 'characteristicUuid',
isNotification: 'isNotification',
state: 'state',
descriptors: 'descriptors',
descriptorUuid: 'descriptorUuid',
handle: 'handle'
};
[
{ type: 'stateChange', event: ['stateChange', mainEvent.state] },
{ type: 'discover', event: ['discover', mainEvent.peripheralUuid, mainEvent.address, mainEvent.addressType, mainEvent.connectable, mainEvent.advertisement, mainEvent.rssi] },
{ type: 'connect', event: ['connect', mainEvent.peripheralUuid] },
{ type: 'disconnect', event: ['disconnect', mainEvent.peripheralUuid] },
{ type: 'rssiUpdate', event: ['rssiUpdate', mainEvent.peripheralUuid, mainEvent.rssi] },
{ type: 'servicesDiscover', event: ['servicesDiscover', mainEvent.peripheralUuid, mainEvent.serviceUuids] },
{ type: 'includedServicesDiscover', event: ['includedServicesDiscover', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.includedServiceUuids] },
{ type: 'characteristicsDiscover', event: ['characteristicsDiscover', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristics] },
{ type: 'read', event: ['read', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid, null, mainEvent.isNotification] },
{ type: 'write', event: ['write', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid] },
{ type: 'broadcast', event: ['broadcast', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid, mainEvent.state] },
{ type: 'notify', event: ['notify', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid, mainEvent.state] },
{ type: 'descriptorsDiscover', event: ['descriptorsDiscover', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid, mainEvent.descriptors] },
{ type: 'valueRead', event: ['valueRead', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid, mainEvent.descriptorUuid, null] },
{ type: 'valueWrite', event: ['valueWrite', mainEvent.peripheralUuid, mainEvent.serviceUuid, mainEvent.characteristicUuid, mainEvent.descriptorUuid] },
{ type: 'handleRead', event: ['handleRead', mainEvent.peripheralUuid, mainEvent.handle, null] },
{ type: 'handleWrite', event: ['handleWrite', mainEvent.peripheralUuid, mainEvent.handle] },
{ type: 'handleNotify', event: ['handleNotify', mainEvent.peripheralUuid, mainEvent.handle, null] }
].forEach(args => {
const { type, event } = args;
it(`should emit ${type}`, () => {
bindings.emit = sinon.spy();
const eventMsg = Object.assign({ type }, mainEvent);
bindings._onMessage(eventMsg);
assert.calledOnceWithExactly(bindings.emit, ...event);
});
});
});
describe('sendCommand', () => {
it('should not callback without error', () => {
const errorCallback = sinon.spy();
bindings._ws.send = sinon.spy();
bindings._sendCommand({ command: 3 }, errorCallback);
bindings._ws.send.callArgWith(1, null);
assert.calledOnceWithMatch(bindings._ws.send, '{"command":3}', sinon.match.func);
assert.notCalled(errorCallback);
});
it('should callback error', () => {
const errorCallback = sinon.spy();
bindings._ws.send = sinon.spy();
bindings._sendCommand({ command: 3 }, errorCallback);
bindings._ws.send.callArgWith(1, 'error');
assert.calledOnceWithMatch(bindings._ws.send, '{"command":3}', sinon.match.func);
assert.calledOnceWithExactly(errorCallback, 'error');
});
it('should not callback error as it is missing', () => {
bindings._ws.send = sinon.spy();
bindings._sendCommand({ command: 3 });
bindings._ws.send.callArgWith(1, 'error');
assert.calledOnceWithMatch(bindings._ws.send, '{"command":3}', sinon.match.func);
});
});
it('startScanning - should emit scanStart', () => {
const callback = sinon.spy();
const serviceUuids = ['service'];
const allowDuplicates = true;
bindings._sendCommand = sinon.spy();
bindings.on('scanStart', callback);
bindings.startScanning(serviceUuids, allowDuplicates);
const startCommand = {
action: 'startScanning',
serviceUuids,
allowDuplicates
};
assert.calledOnceWithExactly(callback);
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
should(bindings._startScanCommand).deepEqual(startCommand);
});
it('stopScanning - should emit scanStop', () => {
const callback = sinon.spy();
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.on('scanStop', callback);
bindings.stopScanning();
const startCommand = {
action: 'stopScanning'
};
assert.calledOnceWithExactly(callback);
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
should(bindings._startScanCommand).equal(null);
});
it('connect - should send connect command', () => {
const deviceUuid = 'deviceUuid';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.connect(deviceUuid);
const startCommand = {
action: 'connect',
peripheralUuid: 'uuid'
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('disconnect - should send disconnect command', () => {
const deviceUuid = 'deviceUuid';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.disconnect(deviceUuid);
const startCommand = {
action: 'disconnect',
peripheralUuid: 'uuid'
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('updateRssi - should send updateRssi command', () => {
const deviceUuid = 'deviceUuid';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.updateRssi(deviceUuid);
const startCommand = {
action: 'updateRssi',
peripheralUuid: 'uuid'
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('discoverServices - should send discoverServices command', () => {
const deviceUuid = 'deviceUuid';
const uuids = 'uuids';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.discoverServices(deviceUuid, uuids);
const startCommand = {
action: 'discoverServices',
peripheralUuid: 'uuid',
uuids
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('discoverIncludedServices - should send discoverIncludedServices command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const serviceUuids = 'serviceUuids';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.discoverIncludedServices(deviceUuid, serviceUuid, serviceUuids);
const startCommand = {
action: 'discoverIncludedServices',
peripheralUuid: 'uuid',
serviceUuid,
serviceUuids
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('discoverCharacteristics - should send discoverIncludedServices command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuids = 'characteristicUuids';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.discoverCharacteristics(deviceUuid, serviceUuid, characteristicUuids);
const startCommand = {
action: 'discoverCharacteristics',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuids
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('read - should send read command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.read(deviceUuid, serviceUuid, characteristicUuid);
const startCommand = {
action: 'read',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('write - should send write command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
const data = Buffer.from([0x34, 0x12]);
const withoutResponse = true;
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.write(deviceUuid, serviceUuid, characteristicUuid, data, withoutResponse);
const startCommand = {
action: 'write',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid,
data: '3412',
withoutResponse
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('broadcast - should send broadcast command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
const broadcast = true;
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.broadcast(deviceUuid, serviceUuid, characteristicUuid, broadcast);
const startCommand = {
action: 'broadcast',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid,
broadcast
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('notify - should send notify command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
const notify = true;
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.notify(deviceUuid, serviceUuid, characteristicUuid, notify);
const startCommand = {
action: 'notify',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid,
notify
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('discoverDescriptors - should send discoverDescriptors command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.discoverDescriptors(deviceUuid, serviceUuid, characteristicUuid);
const startCommand = {
action: 'discoverDescriptors',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('readValue - should send readValue command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
const descriptorUuid = 'descriptorUuid';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.readValue(deviceUuid, serviceUuid, characteristicUuid, descriptorUuid);
const startCommand = {
action: 'readValue',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid,
descriptorUuid
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('writeValue - should send writeValue command', () => {
const deviceUuid = 'deviceUuid';
const serviceUuid = 'serviceUuid';
const characteristicUuid = 'characteristicUuid';
const descriptorUuid = 'descriptorUuid';
const data = Buffer.from([0x34, 0x12]);
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.writeValue(deviceUuid, serviceUuid, characteristicUuid, descriptorUuid, data);
const startCommand = {
action: 'writeValue',
peripheralUuid: 'uuid',
serviceUuid,
characteristicUuid,
descriptorUuid,
data: '3412'
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('readHandle - should send readHandle command', () => {
const deviceUuid = 'deviceUuid';
const handle = 'handle';
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.readHandle(deviceUuid, handle);
const startCommand = {
action: 'readHandle',
peripheralUuid: 'uuid',
handle
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
it('writeHandle - should send writeHandle command', () => {
const deviceUuid = 'deviceUuid';
const handle = 'handle';
const data = Buffer.from([0x34, 0x12]);
const withoutResponse = true;
bindings._peripherals[deviceUuid] = { uuid: 'uuid' };
bindings._startScanCommand = 'command';
bindings._sendCommand = sinon.spy();
bindings.writeHandle(deviceUuid, handle, data, withoutResponse);
const startCommand = {
action: 'writeHandle',
peripheralUuid: 'uuid',
handle,
data: '3412',
withoutResponse
};
assert.calledOnceWithExactly(bindings._sendCommand, startCommand);
});
});