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')
|
2019-12-10 14:02:18 +01:00
|
|
|
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')
|
2019-12-06 14:28:52 +01:00
|
|
|
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 = {
|
2019-12-06 14:28:52 +01:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
|
2019-12-10 14:02:18 +01:00
|
|
|
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()
|
2019-12-10 14:02:18 +01:00
|
|
|
const deferred = pDefer()
|
|
|
|
const actions = {
|
2019-12-10 14:45:52 +01:00
|
|
|
1: () => firstDials.promise,
|
|
|
|
2: () => firstDials.promise,
|
2019-12-10 14:02:18 +01:00
|
|
|
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)
|
2019-12-10 14:02:18 +01:00
|
|
|
|
|
|
|
// 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
|
2019-12-10 14:02:18 +01:00
|
|
|
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 = {
|
2019-12-06 14:28:52 +01:00
|
|
|
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 () => {
|
2019-12-06 14:28:52 +01:00
|
|
|
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 = {
|
2019-12-06 14:28:52 +01:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
})
|