js-libp2p/test/dialing/dial-request.spec.js

226 lines
6.7 KiB
JavaScript
Raw Permalink Normal View History

2019-12-06 14:26:21 +01:00
'use strict'
/* eslint-env mocha */
2020-10-15 15:31:33 +01:00
const { expect } = require('aegir/utils/chai')
2019-12-06 14:26:21 +01:00
const sinon = require('sinon')
const { AbortError } = require('libp2p-interfaces/src/transport/errors')
const AbortController = require('abort-controller')
const AggregateError = require('aggregate-error')
const pDefer = require('p-defer')
const delay = require('delay')
2019-12-06 14:26:21 +01:00
2020-12-10 14:48:14 +01:00
const DialRequest = require('../../src/dialer/dial-request')
2019-12-06 14:26:21 +01:00
const createMockConnection = require('../utils/mockConnection')
const error = new Error('dial failes')
2019-12-06 14:26:21 +01:00
describe('Dial Request', () => {
it('should end when a single multiaddr dials succeeds', async () => {
const mockConnection = await createMockConnection()
const actions = {
1: () => Promise.reject(error),
2: () => Promise.resolve(mockConnection),
3: () => Promise.reject(error)
2019-12-06 14:26:21 +01:00
}
const dialAction = (num) => actions[num]()
const tokens = ['a', 'b']
const controller = new AbortController()
const dialer = {
getTokens: () => [...tokens],
releaseToken: () => {}
}
const dialRequest = new DialRequest({
addrs: Object.keys(actions),
dialer,
dialAction
})
sinon.spy(actions, 1)
sinon.spy(actions, 2)
sinon.spy(actions, 3)
sinon.spy(dialer, 'releaseToken')
const result = await dialRequest.run({ signal: controller.signal })
expect(result).to.equal(mockConnection)
expect(actions[1]).to.have.property('callCount', 1)
expect(actions[2]).to.have.property('callCount', 1)
expect(actions[3]).to.have.property('callCount', 0)
expect(dialer.releaseToken).to.have.property('callCount', tokens.length)
})
it('should release tokens when all addr dials have started', async () => {
const mockConnection = await createMockConnection()
2019-12-10 14:45:52 +01:00
const firstDials = pDefer()
const deferred = pDefer()
const actions = {
2019-12-10 14:45:52 +01:00
1: () => firstDials.promise,
2: () => firstDials.promise,
3: () => deferred.promise
}
const dialAction = (num) => actions[num]()
const tokens = ['a', 'b']
const controller = new AbortController()
const dialer = {
getTokens: () => [...tokens],
releaseToken: () => {}
}
const dialRequest = new DialRequest({
addrs: Object.keys(actions),
dialer,
dialAction
})
sinon.spy(actions, 1)
sinon.spy(actions, 2)
sinon.spy(actions, 3)
sinon.spy(dialer, 'releaseToken')
dialRequest.run({ signal: controller.signal })
// Let the first dials run
2019-12-10 14:45:52 +01:00
await delay(0)
// Finish the first 2 dials
firstDials.reject(error)
await delay(0)
// Only 1 dial should remain, so 1 token should have been released
expect(actions[1]).to.have.property('callCount', 1)
expect(actions[2]).to.have.property('callCount', 1)
expect(actions[3]).to.have.property('callCount', 1)
expect(dialer.releaseToken).to.have.property('callCount', 1)
2019-12-10 14:45:52 +01:00
// Finish the dial and release the 2nd token
deferred.resolve(mockConnection)
await delay(0)
expect(dialer.releaseToken).to.have.property('callCount', 2)
})
2019-12-06 14:26:21 +01:00
it('should throw an AggregateError if all dials fail', async () => {
const actions = {
1: () => Promise.reject(error),
2: () => Promise.reject(error),
3: () => Promise.reject(error)
2019-12-06 14:26:21 +01:00
}
const dialAction = (num) => actions[num]()
const addrs = Object.keys(actions)
const tokens = ['a', 'b']
const controller = new AbortController()
const dialer = {
getTokens: () => [...tokens],
releaseToken: () => {}
}
const dialRequest = new DialRequest({
addrs,
dialer,
dialAction
})
sinon.spy(actions, 1)
sinon.spy(actions, 2)
sinon.spy(actions, 3)
sinon.spy(dialer, 'getTokens')
sinon.spy(dialer, 'releaseToken')
try {
await dialRequest.run({ signal: controller.signal })
expect.fail('Should have thrown')
} catch (err) {
expect(err).to.be.an.instanceof(AggregateError)
}
expect(actions[1]).to.have.property('callCount', 1)
expect(actions[2]).to.have.property('callCount', 1)
expect(actions[3]).to.have.property('callCount', 1)
expect(dialer.getTokens.calledWith(addrs.length)).to.equal(true)
expect(dialer.releaseToken).to.have.property('callCount', tokens.length)
})
it('should handle a large number of addrs', async () => {
const reject = sinon.stub().callsFake(() => Promise.reject(error))
2019-12-06 14:26:21 +01:00
const actions = {}
const addrs = [...new Array(25)].map((_, index) => index + 1)
addrs.forEach(addr => {
actions[addr] = reject
})
const dialAction = (addr) => actions[addr]()
const tokens = ['a', 'b']
const controller = new AbortController()
const dialer = {
getTokens: () => [...tokens],
releaseToken: () => {}
}
const dialRequest = new DialRequest({
addrs,
dialer,
dialAction
})
sinon.spy(dialer, 'releaseToken')
try {
await dialRequest.run({ signal: controller.signal })
expect.fail('Should have thrown')
} catch (err) {
expect(err).to.be.an.instanceof(AggregateError)
}
expect(reject).to.have.property('callCount', addrs.length)
expect(dialer.releaseToken).to.have.property('callCount', tokens.length)
})
it('should abort all dials when its signal is aborted', async () => {
const deferToAbort = ({ signal }) => {
if (signal.aborted) throw new Error('already aborted')
const deferred = pDefer()
const onAbort = () => {
deferred.reject(new AbortError())
signal.removeEventListener('abort', onAbort)
}
signal.addEventListener('abort', onAbort)
return deferred.promise
}
const actions = {
1: deferToAbort,
2: deferToAbort,
3: deferToAbort
2019-12-06 14:26:21 +01:00
}
const dialAction = (num, opts) => actions[num](opts)
const addrs = Object.keys(actions)
const tokens = ['a', 'b']
const controller = new AbortController()
const dialer = {
getTokens: () => [...tokens],
releaseToken: () => {}
}
const dialRequest = new DialRequest({
addrs,
dialer,
dialAction
})
sinon.spy(actions, 1)
sinon.spy(actions, 2)
sinon.spy(actions, 3)
sinon.spy(dialer, 'getTokens')
sinon.spy(dialer, 'releaseToken')
try {
setTimeout(() => controller.abort(), 100)
await dialRequest.run({ signal: controller.signal })
expect.fail('dial should have failed')
} catch (err) {
expect(err).to.be.an.instanceof(AggregateError)
}
expect(actions[1]).to.have.property('callCount', 1)
expect(actions[2]).to.have.property('callCount', 1)
expect(actions[3]).to.have.property('callCount', 1)
expect(dialer.getTokens.calledWith(addrs.length)).to.equal(true)
expect(dialer.releaseToken).to.have.property('callCount', tokens.length)
})
})