UNPKG

@abandonware/noble

Version:

A Node.js BLE (Bluetooth Low Energy) central library.

1,617 lines (1,250 loc) 54.1 kB
const proxyquire = require('proxyquire').noCallThru(); const should = require('should'); const sinon = require('sinon'); const { assert, fake } = sinon; describe('hci-socket bindings', () => { const AclStream = sinon.stub(); const Gap = sinon.stub(); const gattOnSpy = sinon.spy(); const gattExchangeMtuSpy = sinon.spy(); const Gatt = sinon.stub(); Gatt.prototype.on = gattOnSpy; Gatt.prototype.exchangeMtu = gattExchangeMtuSpy; const createLeConnSpy = sinon.spy(); const Hci = sinon.stub(); Hci.prototype.createLeConn = createLeConnSpy; Hci.STATUS_MAPPER = { 1: 'custom mapper' }; const signalingOnSpy = sinon.spy(); const Signaling = sinon.stub(); Signaling.prototype.on = signalingOnSpy; const Bindings = proxyquire('../../../lib/hci-socket/bindings', { './acl-stream': AclStream, './gap': Gap, './gatt': Gatt, './hci': Hci, './signaling': Signaling }); let bindings; let clock; const options = {}; beforeEach(() => { sinon.stub(process, 'on'); sinon.stub(process, 'exit'); bindings = new Bindings(options); clock = sinon.useFakeTimers(); }); afterEach(() => { process.on.restore(); process.exit.restore(); clock.restore(); sinon.reset(); }); it('constructor', () => { should(bindings._state).eql(null); should(bindings._addresses).deepEqual({}); should(bindings._addresseTypes).deepEqual({}); should(bindings._connectable).deepEqual({}); should(bindings._pendingConnectionUuid).eql(null); should(bindings._connectionQueue).deepEqual([]); should(bindings._handles).deepEqual({}); should(bindings._gatts).deepEqual({}); should(bindings._aclStreams).deepEqual({}); should(bindings._signalings).deepEqual({}); should(bindings._hci).instanceOf(Hci); should(bindings._gap).instanceOf(Gap); assert.calledOnce(Hci); assert.calledWith(Hci, options); assert.calledOnce(Gap); assert.calledWith(Gap, bindings._hci); }); describe('onSigInt', () => { it('should exit', () => { const sigIntListeners = process.listeners('SIGINT'); bindings.onSigIntBinded = sigIntListeners[sigIntListeners.length - 1]; bindings.onSigInt(); assert.calledOnceWithExactly(process.exit, 1); }); it('should not exit', () => { bindings.onSigIntBinded = sinon.spy(); bindings.onSigInt(); assert.notCalled(process.exit); }); }); it('setScanParameters', () => { bindings._gap.setScanParameters = fake.resolves(null); bindings.setScanParameters('interval', 'window'); assert.calledOnce(bindings._gap.setScanParameters); assert.calledWith(bindings._gap.setScanParameters, 'interval', 'window'); }); describe('startScanning', () => { it('no args', () => { bindings._gap.startScanning = fake.resolves(null); bindings.startScanning(); should(bindings._scanServiceUuids).deepEqual([]); assert.calledOnce(bindings._gap.startScanning); assert.calledWith(bindings._gap.startScanning, undefined); }); it('with args', () => { bindings._gap.startScanning = fake.resolves(null); bindings.startScanning(['uuid'], true); should(bindings._scanServiceUuids).deepEqual(['uuid']); assert.calledOnce(bindings._gap.startScanning); assert.calledWith(bindings._gap.startScanning, true); }); }); it('stopScanning', () => { bindings._gap.stopScanning = fake.resolves(null); bindings.stopScanning(); assert.calledOnce(bindings._gap.stopScanning); }); describe('connect', () => { it('missing peripheral, no queue', () => { bindings._hci.createLeConn = fake.resolves(null); bindings.connect('peripheralUuid', 'parameters'); should(bindings._pendingConnectionUuid).eql('peripheralUuid'); assert.calledOnce(bindings._hci.createLeConn); assert.calledWith(bindings._hci.createLeConn, undefined, undefined, 'parameters'); }); it('existing peripheral, no queue', () => { bindings._hci.createLeConn = fake.resolves(null); bindings._addresses = { peripheralUuid: 'address' }; bindings._addresseTypes = { peripheralUuid: 'addressType' }; bindings.connect('peripheralUuid', 'parameters'); should(bindings._pendingConnectionUuid).eql('peripheralUuid'); assert.calledOnce(bindings._hci.createLeConn); assert.calledWith(bindings._hci.createLeConn, 'address', 'addressType', 'parameters'); }); it('missing peripheral, with queue', () => { bindings._pendingConnectionUuid = 'pending-uuid'; bindings.connect('peripheralUuid', 'parameters'); should(bindings._connectionQueue).deepEqual([{ id: 'peripheralUuid', params: 'parameters' }]); }); }); describe('disconnect', () => { it('missing handle', () => { bindings._hci.disconnect = fake.resolves(null); bindings.disconnect('peripheralUuid'); assert.calledOnce(bindings._hci.disconnect); assert.calledWith(bindings._hci.disconnect, undefined); }); it('existing handle', () => { bindings._handles = { peripheralUuid: 'handle' }; bindings._hci.disconnect = fake.resolves(null); bindings.disconnect('peripheralUuid'); assert.calledOnce(bindings._hci.disconnect); assert.calledWith(bindings._hci.disconnect, 'handle'); }); }); describe('cancel', () => { it('missing handle', () => { bindings._connectionQueue.push({ id: 'anotherPeripheralUuid' }); bindings._hci.cancelConnect = fake.resolves(null); bindings.cancelConnect('peripheralUuid'); should(bindings._connectionQueue).size(1); assert.calledOnce(bindings._hci.cancelConnect); assert.calledWith(bindings._hci.cancelConnect, undefined); }); it('existing handle', () => { bindings._handles = { peripheralUuid: 'handle' }; bindings._connectionQueue.push({ id: 'anotherPeripheralUuid' }); bindings._connectionQueue.push({ id: 'peripheralUuid' }); bindings._hci.cancelConnect = fake.resolves(null); bindings.cancelConnect('peripheralUuid'); should(bindings._connectionQueue).size(1); assert.calledOnce(bindings._hci.cancelConnect); assert.calledWith(bindings._hci.cancelConnect, 'handle'); }); }); it('reset', () => { bindings._hci.reset = fake.resolves(null); bindings.reset(); assert.calledOnce(bindings._hci.reset); }); describe('updateRssi', () => { it('missing handle', () => { bindings._hci.readRssi = fake.resolves(null); bindings.updateRssi('peripheralUuid'); assert.calledOnce(bindings._hci.readRssi); assert.calledWith(bindings._hci.readRssi, undefined); }); it('existing handle', () => { bindings._handles = { peripheralUuid: 'handle' }; bindings._hci.readRssi = fake.resolves(null); bindings.updateRssi('peripheralUuid'); assert.calledOnce(bindings._hci.readRssi); assert.calledWith(bindings._hci.readRssi, 'handle'); }); }); it('init', () => { bindings._gap.on = fake.resolves(null); bindings._hci.on = fake.resolves(null); bindings._hci.init = fake.resolves(null); bindings.init(); assert.callCount(bindings._gap.on, 4); assert.callCount(bindings._hci.on, 8); assert.calledOnce(bindings._hci.init); assert.calledTwice(process.on); }); describe('onExit', () => { it('no handles', () => { bindings._gap.stopScanning = fake.resolves(null); bindings.onExit(); assert.calledOnce(bindings._gap.stopScanning); }); it('with handles', () => { bindings._gap.stopScanning = fake.resolves(null); bindings._hci.disconnect = fake.resolves(null); bindings._aclStreams = [1, 2, 3]; bindings.onExit(); assert.calledOnce(bindings._gap.stopScanning); assert.calledThrice(bindings._hci.disconnect); }); }); describe('onStateChange', () => { it('same state', () => { const stateChange = fake.resolves(null); bindings._state = 'state'; bindings.on('stateChange', stateChange); bindings.onStateChange('state'); assert.notCalled(stateChange); }); it('new state', () => { const stateChange = fake.resolves(null); bindings._state = 'state'; bindings.on('stateChange', stateChange); bindings.onStateChange('newState'); assert.calledOnce(stateChange); assert.calledWith(stateChange, 'newState'); }); it('unauthorized', () => { const stateChange = fake.resolves(null); bindings._state = 'state'; bindings.on('stateChange', stateChange); bindings.onStateChange('unauthorized'); assert.calledOnce(stateChange); assert.calledWith(stateChange, 'unauthorized'); }); it('unsupported', () => { const stateChange = fake.resolves(null); bindings._state = 'state'; bindings.on('stateChange', stateChange); bindings.onStateChange('unsupported'); assert.calledOnce(stateChange); assert.calledWith(stateChange, 'unsupported'); }); }); it('onAddressChange', () => { const onAddressChange = fake.resolves(null); bindings.on('addressChange', onAddressChange); bindings.onAddressChange('newAddress'); assert.calledOnce(onAddressChange); assert.calledWith(onAddressChange, 'newAddress'); }); it('onScanParametersSet', () => { const onScanParametersSet = fake.resolves(null); bindings.on('scanParametersSet', onScanParametersSet); bindings.onScanParametersSet(); assert.calledOnce(onScanParametersSet); }); it('onScanStart', () => { const onScanStart = fake.resolves(null); bindings.on('scanStart', onScanStart); bindings.onScanStart('filterDuplicates'); assert.calledOnce(onScanStart); assert.calledWith(onScanStart, 'filterDuplicates'); }); it('onScanStop', () => { const onScanStop = fake.resolves(null); bindings.on('scanStop', onScanStop); bindings.onScanStop(); assert.calledOnce(onScanStop); }); describe('onDiscover', () => { it('new device, no scanServiceUuids', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = []; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = 'advertisement'; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); const uuid = 'addressasmac'; should(bindings._addresses).deepEqual({ [uuid]: address }); should(bindings._addresseTypes).deepEqual({ [uuid]: addressType }); should(bindings._connectable).deepEqual({ [uuid]: connectable }); assert.calledOnce(onDiscover); assert.calledWith(onDiscover, uuid, address, addressType, connectable, advertisement, rssi); }); it('new device, with matching scanServiceUuids', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = ['service-uuid']; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = { serviceUuids: ['service-uuid'] }; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); const uuid = 'addressasmac'; should(bindings._addresses).deepEqual({ [uuid]: address }); should(bindings._addresseTypes).deepEqual({ [uuid]: addressType }); should(bindings._connectable).deepEqual({ [uuid]: connectable }); assert.calledOnce(onDiscover); assert.calledWith(onDiscover, uuid, address, addressType, connectable, advertisement, rssi); }); it('new device, with non-matching scanServiceUuids', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = ['service-uuid']; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = { serviceUuids: ['another-service-uuid'] }; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); should(bindings._addresses).deepEqual({}); should(bindings._addresseTypes).deepEqual({}); should(bindings._connectable).deepEqual({}); assert.notCalled(onDiscover); }); it('new device, with service data on advertisement', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = ['service-uuid']; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = { serviceData: [{ uuid: 'service-uuid' }] }; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); const uuid = 'addressasmac'; should(bindings._addresses).deepEqual({ [uuid]: address }); should(bindings._addresseTypes).deepEqual({ [uuid]: addressType }); should(bindings._connectable).deepEqual({ [uuid]: connectable }); assert.calledOnce(onDiscover); assert.calledWith(onDiscover, uuid, address, addressType, connectable, advertisement, rssi); }); it('new device, non matching service data on advertisement', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = ['service-uuid']; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = { serviceData: [{ uuid: 'another-service-uuid' }] }; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); should(bindings._addresses).deepEqual({}); should(bindings._addresseTypes).deepEqual({}); should(bindings._connectable).deepEqual({}); assert.notCalled(onDiscover); }); it('new device, no services on advertisement', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = ['service-uuid']; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = {}; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); should(bindings._addresses).deepEqual({}); should(bindings._addresseTypes).deepEqual({}); should(bindings._connectable).deepEqual({}); assert.notCalled(onDiscover); }); it('new device, undefined _scanServiceUuids', () => { const onDiscover = fake.resolves(null); bindings.on('discover', onDiscover); bindings._scanServiceUuids = undefined; const status = 'status'; const address = 'address:as:mac'; const addressType = 'addressType'; const connectable = 'connectable'; const advertisement = { serviceData: [{ uuid: 'service-uuid' }] }; const rssi = 'rssi'; bindings.onDiscover(status, address, addressType, connectable, advertisement, rssi); should(bindings._addresses).deepEqual({}); should(bindings._addresseTypes).deepEqual({}); should(bindings._connectable).deepEqual({}); assert.notCalled(onDiscover); }); }); describe('onLeConnComplete', () => { it('not on master node', () => { const status = 1; const handle = 'handle'; const role = 'not-master'; const addressType = 'addressType'; const address = 'address'; const connectCallback = sinon.spy(); bindings.on('connect', connectCallback); bindings.onLeConnComplete(status, handle, role, addressType, address); assert.notCalled(connectCallback); }); it('with right status on master node', () => { const status = 0; const handle = 'handle'; const role = 0; const addressType = 'addressType'; const address = 'address:split:by:separator'; const connectCallback = sinon.spy(); bindings.on('connect', connectCallback); bindings.onLeConnComplete(status, handle, role, addressType, address); clock.tick(0); assert.calledOnce(AclStream); assert.calledOnce(Gatt); assert.calledOnce(Signaling); assert.callCount(gattOnSpy, 17); assert.calledWithMatch(gattOnSpy, 'mtu', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'servicesDiscover', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'servicesDiscovered', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'includedServicesDiscover', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'characteristicsDiscover', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'characteristicsDiscovered', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'read', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'write', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'broadcast', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'notify', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'notification', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'descriptorsDiscover', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'valueRead', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'valueWrite', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'handleRead', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'handleWrite', sinon.match.func); assert.calledWithMatch(gattOnSpy, 'handleNotify', sinon.match.func); assert.calledOnceWithMatch(signalingOnSpy, 'connectionParameterUpdateRequest', sinon.match.func); assert.calledOnceWithExactly(gattExchangeMtuSpy); assert.calledOnceWithExactly(connectCallback, 'addresssplitbyseparator', null); should(bindings._pendingConnectionUuid).equal(null); }); it('with invalid status on master node', () => { const status = 1; const handle = 'handle'; const role = 0; const addressType = 'addressType'; const address = 'address:split:by:separator'; const connectCallback = sinon.spy(); bindings._pendingConnectionUuid = 'pending_uuid'; bindings.on('connect', connectCallback); bindings.onLeConnComplete(status, handle, role, addressType, address); assert.notCalled(AclStream); assert.notCalled(Gatt); assert.notCalled(Signaling); assert.calledOnceWithMatch(connectCallback, 'pending_uuid', sinon.match({ message: 'custom mapper (0x1)' })); should(bindings._pendingConnectionUuid).equal(null); }); it('with unmapped status on master node', () => { const status = 2; const handle = 'handle'; const role = 0; const addressType = 'addressType'; const address = 'address:split:by:separator'; const connectCallback = sinon.spy(); bindings._pendingConnectionUuid = 'pending_uuid'; bindings.on('connect', connectCallback); bindings.onLeConnComplete(status, handle, role, addressType, address); assert.notCalled(AclStream); assert.notCalled(Gatt); assert.notCalled(Signaling); assert.calledOnceWithExactly(connectCallback, 'pending_uuid', sinon.match({ message: 'HCI Error: Unknown (0x2)' })); should(bindings._pendingConnectionUuid).equal(null); }); it('with connection queue', () => { const status = 0; const handle = 'handle'; const role = 0; const addressType = 'addressType'; const address = 'address:split:by:separator'; const connectCallback = sinon.spy(); bindings._connectionQueue = [{ id: 'queuedId', params: { p1: 'p1' } }]; bindings._addresses = { queuedId: 'queuedAddress' }; bindings._addresseTypes = { queuedId: 'queuedAddressType' }; bindings.on('connect', connectCallback); bindings.onLeConnComplete(status, handle, role, addressType, address); assert.calledOnceWithExactly(connectCallback, 'addresssplitbyseparator', null); assert.calledOnceWithExactly(createLeConnSpy, 'queuedAddress', 'queuedAddressType', { p1: 'p1' }); should(bindings._pendingConnectionUuid).equal('queuedId'); }); }); describe('onDisconnComplete', () => { it('handle not found', () => { const disconnectCallback = sinon.spy(); bindings.on('disconnect', disconnectCallback); bindings.onDisconnComplete('missing', 'reason'); assert.notCalled(disconnectCallback); }); it('existing handle', () => { const disconnectCallback = sinon.spy(); const handle = 'handle'; const anotherHandle = 'another_handle'; const uuid = 'uuid'; const anotherUuid = 'another_uuid'; const reason = 'reason'; const gattSpy = { removeAllListeners: sinon.spy() }; const signalingSpy = { removeAllListeners: sinon.spy() }; // Init expected bindings._handles[uuid] = uuid; bindings._handles[handle] = uuid; bindings._handles[anotherUuid] = uuid; bindings._handles[anotherHandle] = anotherUuid; bindings._aclStreams[handle] = []; bindings._aclStreams[anotherHandle] = []; bindings._gatts[handle] = gattSpy; bindings._gatts[uuid] = gattSpy; bindings._gatts[anotherHandle] = gattSpy; bindings._gatts[anotherUuid] = gattSpy; bindings._signalings[uuid] = signalingSpy; bindings._signalings[handle] = signalingSpy; bindings._signalings[anotherUuid] = signalingSpy; bindings._signalings[anotherHandle] = signalingSpy; bindings.on('disconnect', disconnectCallback); bindings.onDisconnComplete(handle, reason); assert.calledOnceWithExactly(disconnectCallback, uuid, reason); assert.calledOnceWithExactly(gattSpy.removeAllListeners); assert.calledOnceWithExactly(signalingSpy.removeAllListeners); should(bindings._handles).not.have.keys(uuid, handle); should(bindings._aclStreams).not.have.keys(handle); should(bindings._gatts).not.have.keys(uuid, handle); should(bindings._signalings).not.have.keys(uuid, handle); }); }); describe('onEncryptChange', () => { it('missing handle', () => { const handle = 'handle'; const anotherHandle = 'another_handle'; const encrypt = 'encrypt'; const aclSpy = { pushEncrypt: sinon.spy() }; bindings._aclStreams[handle] = aclSpy; bindings.onEncryptChange(anotherHandle, encrypt); assert.notCalled(aclSpy.pushEncrypt); }); it('existing handle', () => { const handle = 'handle'; const encrypt = 'encrypt'; const aclSpy = { pushEncrypt: sinon.spy() }; bindings._aclStreams[handle] = aclSpy; bindings.onEncryptChange(handle, encrypt); assert.calledOnceWithExactly(aclSpy.pushEncrypt, encrypt); }); it('existing handle no encrypt', () => { const handle = 'handle'; const aclSpy = { pushEncrypt: sinon.spy() }; bindings._aclStreams[handle] = aclSpy; bindings.onEncryptChange(handle); assert.calledOnceWithExactly(aclSpy.pushEncrypt, undefined); }); }); it('onMtu', () => { const address = 'this:is:an:address'; const rssi = 'rssi'; const callback = sinon.spy(); bindings.on('onMtu', callback); bindings.onMtu(address, rssi); assert.calledOnceWithExactly(callback, 'thisisanaddress', rssi); }); it('onRssiRead', () => { const handle = 'handle'; const rssi = 'rssi'; const callback = sinon.spy(); bindings._handles[handle] = 'binding_handle'; bindings.on('rssiUpdate', callback); bindings.onRssiRead(handle, rssi); assert.calledOnceWithExactly(callback, 'binding_handle', rssi); }); describe('onAclDataPkt', () => { it('missing handle', () => { const handle = 'handle'; const anotherHandle = 'another_handle'; const cid = 'cid'; const data = 'data'; const aclSpy = { push: sinon.spy() }; bindings._aclStreams[handle] = aclSpy; bindings.onAclDataPkt(anotherHandle, cid, data); assert.notCalled(aclSpy.push); }); it('existing handle', () => { const handle = 'handle'; const cid = 'cid'; const data = 'data'; const aclSpy = { push: sinon.spy() }; bindings._aclStreams[handle] = aclSpy; bindings.onAclDataPkt(handle, cid, data); assert.calledOnceWithExactly(aclSpy.push, cid, data); }); it('existing handle no cid no data', () => { const handle = 'handle'; const aclSpy = { push: sinon.spy() }; bindings._aclStreams[handle] = aclSpy; bindings.onAclDataPkt(handle); assert.calledOnceWithExactly(aclSpy.push, undefined, undefined); }); }); describe('addService', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const service = 'service'; const gatt = { addService: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.addService(peripheralUuid, service); assert.notCalled(gatt.addService); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const service = 'service'; const gatt = { addService: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.addService(peripheralUuid, service); assert.calledOnceWithExactly(gatt.addService, service); }); it('existing gatt no service', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { addService: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.addService(peripheralUuid); assert.calledOnceWithExactly(gatt.addService, undefined); }); }); describe('discoverServices', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const uuids = 'uuids'; const gatt = { discoverServices: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.discoverServices(peripheralUuid, uuids); assert.notCalled(gatt.discoverServices); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const uuids = 'uuids'; const gatt = { discoverServices: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverServices(peripheralUuid, uuids); assert.calledOnceWithExactly(gatt.discoverServices, uuids); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { discoverServices: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverServices(peripheralUuid); assert.calledOnceWithExactly(gatt.discoverServices, []); }); }); it('onServicesDiscovered', () => { const address = 'this:is:an:address'; const serviceUuids = 'serviceUuids'; const callback = sinon.spy(); bindings.on('servicesDiscover', callback); bindings.onServicesDiscovered(address, serviceUuids); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuids); }); it('onServicesDiscoveredEX', () => { const address = 'this:is:an:address'; const serviceUuids = 'serviceUuids'; const callback = sinon.spy(); bindings.on('servicesDiscovered', callback); bindings.onServicesDiscoveredEX(address, serviceUuids); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuids); }); describe('discoverIncludedServices', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'service-uuid'; const serviceUuids = 'serviceUuids'; const gatt = { discoverIncludedServices: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.discoverIncludedServices(peripheralUuid, serviceUuid, serviceUuids); assert.notCalled(gatt.discoverIncludedServices); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'service-uuid'; const serviceUuids = 'serviceUuids'; const gatt = { discoverIncludedServices: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverIncludedServices(peripheralUuid, serviceUuid, serviceUuids); assert.calledOnceWithExactly(gatt.discoverIncludedServices, serviceUuid, serviceUuids); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'service-uuid'; const gatt = { discoverIncludedServices: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverIncludedServices(peripheralUuid, serviceUuid); assert.calledOnceWithExactly(gatt.discoverIncludedServices, serviceUuid, []); }); }); it('onIncludedServicesDiscovered', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const includedServiceUuids = 'includedServiceUuids'; const callback = sinon.spy(); bindings.on('includedServicesDiscover', callback); bindings.onIncludedServicesDiscovered(address, serviceUuid, includedServiceUuids); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, includedServiceUuids); }); describe('addCharacteristics', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristics = 'characteristics'; const gatt = { addCharacteristics: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.addCharacteristics(peripheralUuid, serviceUuid, characteristics); assert.notCalled(gatt.addCharacteristics); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristics = 'characteristics'; const gatt = { addCharacteristics: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.addCharacteristics(peripheralUuid, serviceUuid, characteristics); assert.calledOnceWithExactly(gatt.addCharacteristics, serviceUuid, characteristics); }); it('existing gatt no serviceUuid no characteristics', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { addCharacteristics: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.addCharacteristics(peripheralUuid); assert.calledOnceWithExactly(gatt.addCharacteristics, undefined, undefined); }); }); describe('discoverCharacteristics', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuids = 'characteristics'; const gatt = { discoverCharacteristics: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.discoverCharacteristics(peripheralUuid, serviceUuid, characteristicUuids); assert.notCalled(gatt.discoverCharacteristics); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuids = 'characteristics'; const gatt = { discoverCharacteristics: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverCharacteristics(peripheralUuid, serviceUuid, characteristicUuids); assert.calledOnceWithExactly(gatt.discoverCharacteristics, serviceUuid, characteristicUuids); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const gatt = { discoverCharacteristics: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverCharacteristics(peripheralUuid, serviceUuid); assert.calledOnceWithExactly(gatt.discoverCharacteristics, serviceUuid, []); }); }); it('onCharacteristicsDiscovered', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristics = 'characteristics'; const callback = sinon.spy(); bindings.on('characteristicsDiscover', callback); bindings.onCharacteristicsDiscovered(address, serviceUuid, characteristics); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristics); }); it('onCharacteristicsDiscoveredEX', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristics = 'characteristics'; const callback = sinon.spy(); bindings.on('characteristicsDiscovered', callback); bindings.onCharacteristicsDiscoveredEX(address, serviceUuid, characteristics); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristics); }); describe('read', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const gatt = { read: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.read(peripheralUuid, serviceUuid, characteristicUuid); assert.notCalled(gatt.read); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const gatt = { read: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.read(peripheralUuid, serviceUuid, characteristicUuid); assert.calledOnceWithExactly(gatt.read, serviceUuid, characteristicUuid); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { read: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.read(peripheralUuid); assert.calledOnceWithExactly(gatt.read, undefined, undefined); }); }); it('onRead', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const data = 'data'; const callback = sinon.spy(); bindings.on('read', callback); bindings.onRead(address, serviceUuid, characteristicUuid, data); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, data, false); }); describe('write', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const data = 'data'; const withoutResponse = true; const gatt = { write: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.write(peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse); assert.notCalled(gatt.write); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const data = 'data'; const withoutResponse = true; const gatt = { write: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.write(peripheralUuid, serviceUuid, characteristicUuid, data, withoutResponse); assert.calledOnceWithExactly(gatt.write, serviceUuid, characteristicUuid, data, withoutResponse); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { write: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.write(peripheralUuid); assert.calledOnceWithExactly(gatt.write, undefined, undefined, undefined, undefined); }); }); it('onWrite', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const callback = sinon.spy(); bindings.on('write', callback); bindings.onWrite(address, serviceUuid, characteristicUuid); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid); }); describe('broadcast', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const broadcast = 'broadcast'; const gatt = { broadcast: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.broadcast(peripheralUuid, serviceUuid, characteristicUuid, broadcast); assert.notCalled(gatt.broadcast); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const broadcast = 'broadcast'; const gatt = { broadcast: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.broadcast(peripheralUuid, serviceUuid, characteristicUuid, broadcast); assert.calledOnceWithExactly(gatt.broadcast, serviceUuid, characteristicUuid, broadcast); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { broadcast: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.broadcast(peripheralUuid); assert.calledOnceWithExactly(gatt.broadcast, undefined, undefined, undefined); }); }); it('onBroadcast', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const state = 'data'; const callback = sinon.spy(); bindings.on('broadcast', callback); bindings.onBroadcast(address, serviceUuid, characteristicUuid, state); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, state); }); describe('notify', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const notify = 'notify'; const gatt = { notify: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.notify(peripheralUuid, serviceUuid, characteristicUuid, notify); assert.notCalled(gatt.notify); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const notify = 'notify'; const gatt = { notify: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.notify(peripheralUuid, serviceUuid, characteristicUuid, notify); assert.calledOnceWithExactly(gatt.notify, serviceUuid, characteristicUuid, notify); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { notify: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.notify(peripheralUuid); assert.calledOnceWithExactly(gatt.notify, undefined, undefined, undefined); }); }); it('onNotify', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const state = 'data'; const callback = sinon.spy(); bindings.on('notify', callback); bindings.onNotify(address, serviceUuid, characteristicUuid, state); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, state); }); it('onNotification', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const data = 'data'; const callback = sinon.spy(); bindings.on('read', callback); bindings.onNotification(address, serviceUuid, characteristicUuid, data); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, data, true); }); describe('discoverDescriptors', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const gatt = { discoverDescriptors: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.discoverDescriptors(peripheralUuid, serviceUuid, characteristicUuid); assert.notCalled(gatt.discoverDescriptors); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const gatt = { discoverDescriptors: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverDescriptors(peripheralUuid, serviceUuid, characteristicUuid); assert.calledOnceWithExactly(gatt.discoverDescriptors, serviceUuid, characteristicUuid); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { discoverDescriptors: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.discoverDescriptors(peripheralUuid); assert.calledOnceWithExactly(gatt.discoverDescriptors, undefined, undefined); }); }); it('onDescriptorsDiscovered', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const descriptorUuids = 'descriptorUuids'; const callback = sinon.spy(); bindings.on('descriptorsDiscover', callback); bindings.onDescriptorsDiscovered(address, serviceUuid, characteristicUuid, descriptorUuids); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, descriptorUuids); }); describe('readValue', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const descriptorUuid = 'descriptorUuid'; const gatt = { readValue: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.readValue(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid); assert.notCalled(gatt.readValue); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const descriptorUuid = 'descriptorUuid'; const gatt = { readValue: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.readValue(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid); assert.calledOnceWithExactly(gatt.readValue, serviceUuid, characteristicUuid, descriptorUuid); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { readValue: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.readValue(peripheralUuid); assert.calledOnceWithExactly(gatt.readValue, undefined, undefined, undefined); }); }); it('onValueRead', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const descriptorUuid = 'descriptorUuid'; const data = 'data'; const callback = sinon.spy(); bindings.on('valueRead', callback); bindings.onValueRead(address, serviceUuid, characteristicUuid, descriptorUuid, data); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, descriptorUuid, data); }); describe('writeValue', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristic'; const descriptorUuid = 'descriptorUuid'; const data = 'data'; const gatt = { writeValue: sinon.spy() }; bindings._handles[peripheralUuid] = anotherHandle; bindings._gatts[handle] = gatt; bindings.writeValue(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data); assert.notCalled(gatt.writeValue); }); it('existing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const descriptorUuid = 'descriptorUuid'; const data = 'data'; const gatt = { writeValue: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.writeValue(peripheralUuid, serviceUuid, characteristicUuid, descriptorUuid, data); assert.calledOnceWithExactly(gatt.writeValue, serviceUuid, characteristicUuid, descriptorUuid, data); }); it('existing gatt no uuids', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const gatt = { writeValue: sinon.spy() }; bindings._handles[peripheralUuid] = handle; bindings._gatts[handle] = gatt; bindings.writeValue(peripheralUuid); assert.calledOnceWithExactly(gatt.writeValue, undefined, undefined, undefined, undefined); }); }); it('onValueWrite', () => { const address = 'this:is:an:address'; const serviceUuid = 'serviceUuid'; const characteristicUuid = 'characteristics'; const descriptorUuid = 'descriptorUuid'; const callback = sinon.spy(); bindings.on('valueWrite', callback); bindings.onValueWrite(address, serviceUuid, characteristicUuid, descriptorUuid); assert.calledOnceWithExactly(callback, 'thisisanaddress', serviceUuid, characteristicUuid, descriptorUuid); }); describe('readHandle', () => { it('missing gatt', () => { const peripheralUuid = 'uuid'; const handle = 'handle'; const anotherHandle = 'another_handle'; const attHandle = 'attHandle'; const gatt = { readHandle: sinon.spy(