import {proxyAgent} from "app/server/lib/ProxyAgent"; import {getAvailablePort} from "app/server/lib/serverUtils"; import {assert} from "chai"; import {HttpsProxyAgent} from "https-proxy-agent"; import {HttpProxyAgent} from "http-proxy-agent"; import fetch from 'node-fetch'; import {TestProxyServer} from 'test/server/lib/helpers/TestProxyServer'; import {serveSomething, Serving} from 'test/server/customUtil'; import {assertMatchArray, captureLog, EnvironmentSnapshot} from "test/server/testUtils"; describe("ProxyAgent", function () { let oldEnv: EnvironmentSnapshot; before(() => { oldEnv = new EnvironmentSnapshot(); }); after(() => { oldEnv.restore(); }); it("should be undefined if no proxy is configured", async function () { delete process.env.GRIST_HTTPS_PROXY; const httpProxy = proxyAgent(new URL("http://localhost:3000")); const httpsProxy = proxyAgent(new URL("https://localhost:3000")); assert.equal(httpProxy, undefined); assert.equal(httpsProxy, undefined); }); it("should be https proxy if proxy is configured and address is https", async function () { process.env.GRIST_HTTPS_PROXY = "https://localhost:9000"; const httpsProxy = proxyAgent(new URL("https://localhost:3000")); assert.instanceOf(httpsProxy, HttpsProxyAgent); }); it("should be https proxy if proxy is configured and address is https", async function () { process.env.GRIST_HTTPS_PROXY = "https://localhost:9000"; const httpsProxy = proxyAgent(new URL("http://localhost:3000")); assert.instanceOf(httpsProxy, HttpProxyAgent); }); describe('proxy error handling', async function() { // Handling requests let serving: Serving; // Proxy server emulation to test possible behaviours of real life server let testProxyServer: TestProxyServer; // Simple fetch using a proxy. function testFetch(relativePath: string) { const url = serving.url + relativePath; return fetch(url, { method: 'POST', headers: {'Content-Type': 'application/json'}, agent: proxyAgent(new URL(url)), }); } beforeEach(async function () { // Set up a server and a proxy. const port = await getAvailablePort(22340); testProxyServer = await TestProxyServer.Prepare(port); serving = await serveSomething(app => { app.post('/200', (_, res) => { res.sendStatus(200); res.end(); }); app.post('/404', (_, res) => { res.sendStatus(404); res.end(); }); }); process.env.GRIST_HTTPS_PROXY = `http://localhost:${port}`; }); afterEach(async function() { await serving.shutdown(); await testProxyServer.dispose().catch(() => {}); }); it("should not report error when proxy is working", async function() { // Normally fetch through proxy works and produces no errors, even for failing status. const logMessages1 = await captureLog('warn', async () => { assert.equal((await testFetch('/200')).status, 200); assert.equal((await testFetch('/404')).status, 404); }); assert.deepEqual(logMessages1, []); }); it("should report error when proxy fails", async function() { // if the proxy isn't listening, fetches produces error messages. await testProxyServer.dispose(); const logMessages2 = await captureLog('warn', async () => { await assert.isRejected(testFetch('/200'), /ECONNREFUSED/); await assert.isRejected(testFetch('/404'), /ECONNREFUSED/); }); // We rely on "ProxyAgent error" message to detect issues with the proxy server. assertMatchArray(logMessages2, [ /warn: ProxyAgent error.*ECONNREFUSED/, /warn: ProxyAgent error.*ECONNREFUSED/, ]); }); }); });