refactor to use http agent interface for tunnels

This commit is contained in:
Roman Shtylman
2018-05-15 14:49:19 -04:00
parent 30fd566c3a
commit c27100b98e
19 changed files with 1041 additions and 1345 deletions

176
lib/TunnelAgent.test.js Normal file
View File

@@ -0,0 +1,176 @@
import http from 'http';
import net from 'net';
import assert from 'assert';
import TunnelAgent from './TunnelAgent';
describe('TunnelAgent', () => {
it('should create an empty agent', async () => {
const agent = new TunnelAgent();
assert.equal(agent.started, false);
const info = await agent.listen();
assert.ok(info.port > 0);
agent.destroy();
});
it('should create a new server and accept connections', async () => {
const agent = new TunnelAgent();
assert.equal(agent.started, false);
const info = await agent.listen();
const sock = net.createConnection({ port: info.port });
// in this test we wait for the socket to be connected
await new Promise(resolve => sock.once('connect', resolve));
const agentSock = await new Promise((resolve, reject) => {
agent.createConnection({}, (err, sock) => {
if (err) {
reject(err);
}
resolve(sock);
});
});
agentSock.write('foo');
await new Promise(resolve => sock.once('readable', resolve));
assert.equal('foo', sock.read().toString());
agent.destroy();
sock.destroy();
});
it('should reject connections over the max', async () => {
const agent = new TunnelAgent({
maxTcpSockets: 2,
});
assert.equal(agent.started, false);
const info = await agent.listen();
const sock1 = net.createConnection({ port: info.port });
const sock2 = net.createConnection({ port: info.port });
// two valid socket connections
const p1 = new Promise(resolve => sock1.once('connect', resolve));
const p2 = new Promise(resolve => sock2.once('connect', resolve));
await Promise.all([p1, p2]);
const sock3 = net.createConnection({ port: info.port });
const p3 = await new Promise(resolve => sock3.once('close', resolve));
agent.destroy();
sock1.destroy();
sock2.destroy();
sock3.destroy();
});
it('should queue createConnection requests', async () => {
const agent = new TunnelAgent();
assert.equal(agent.started, false);
const info = await agent.listen();
// create a promise for the next connection
let fulfilled = false;
const waitSockPromise = new Promise((resolve, reject) => {
agent.createConnection({}, (err, sock) => {
fulfilled = true;
if (err) {
reject(err);
}
resolve(sock);
});
});
// check that the next socket is not yet available
await new Promise(resolve => setTimeout(resolve, 500));
assert(!fulfilled);
// connect, this will make a socket available
const sock = net.createConnection({ port: info.port });
await new Promise(resolve => sock.once('connect', resolve));
const anotherAgentSock = await waitSockPromise;
agent.destroy();
sock.destroy();
});
it('should should emit a online event when a socket connects', async () => {
const agent = new TunnelAgent();
const info = await agent.listen();
const onlinePromise = new Promise(resolve => agent.once('online', resolve));
const sock = net.createConnection({ port: info.port });
await new Promise(resolve => sock.once('connect', resolve));
await onlinePromise;
agent.destroy();
sock.destroy();
});
it('should emit offline event when socket disconnects', async () => {
const agent = new TunnelAgent();
const info = await agent.listen();
const offlinePromise = new Promise(resolve => agent.once('offline', resolve));
const sock = net.createConnection({ port: info.port });
await new Promise(resolve => sock.once('connect', resolve));
sock.end();
await offlinePromise;
agent.destroy();
sock.destroy();
});
it('should emit offline event only when last socket disconnects', async () => {
const agent = new TunnelAgent();
const info = await agent.listen();
const offlinePromise = new Promise(resolve => agent.once('offline', resolve));
const sockA = net.createConnection({ port: info.port });
await new Promise(resolve => sockA.once('connect', resolve));
const sockB = net.createConnection({ port: info.port });
await new Promise(resolve => sockB.once('connect', resolve));
sockA.end();
const timeout = new Promise(resolve => setTimeout(resolve, 500));
await Promise.race([offlinePromise, timeout]);
sockB.end();
await offlinePromise;
agent.destroy();
});
it('should error an http request', async () => {
class ErrorAgent extends http.Agent {
constructor() {
super();
}
createConnection(options, cb) {
cb(new Error('foo'));
}
}
const agent = new ErrorAgent();
const opt = {
host: 'localhost',
port: 1234,
path: '/',
agent: agent,
};
const err = await new Promise((resolve) => {
const req = http.get(opt, (res) => {});
req.once('error', resolve);
});
assert.equal(err.message, 'foo');
});
});