From deeed7e02adb0416acd1dd81e0381bed04438769 Mon Sep 17 00:00:00 2001 From: Fredrik Lindberg Date: Sun, 29 Oct 2023 19:50:58 +0100 Subject: [PATCH] test: do not use fetch when overriding host header --- test/e2e/test_cluster.js | 33 ++-- test/e2e/test_ssh.js | 42 +++-- test/e2e/test_ws.js | 34 ++-- test/system/ingress/test_http_ingress.js | 30 +++- test/unit/test-utils.ts | 7 +- test/unit/transport/test_ssh_transport.ts | 209 +++++++++++++++------- test/unit/transport/test_ws_transport.ts | 65 +++++-- 7 files changed, 298 insertions(+), 122 deletions(-) diff --git a/test/e2e/test_cluster.js b/test/e2e/test_cluster.js index eacef51..752d8e5 100644 --- a/test/e2e/test_cluster.js +++ b/test/e2e/test_cluster.js @@ -1,6 +1,7 @@ import child_process from 'child_process'; import crypto from 'crypto'; import assert from 'assert/strict'; +import http from 'http'; import { setTimeout } from 'timers/promises'; import { createAccount, createEchoServer, exposrCliImageTag, getAuthToken, getTunnel, putTunnel } from './e2e-utils.js'; @@ -145,22 +146,34 @@ describe('Cluster E2E', () => { const ingressUrl = new URL(data.ingress.http.url); - res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${ingressUrl.hostname}:8080` - }, - body: "echo" - }) - - data = await res.text() + let status; + ([status, data] = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": ingressUrl.hostname + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve([res.statusCode, data])}); + }); + req.end('echo'); + })); exposrCliTerminator(); await echoServerTerminate(); node1.terminate(); node2.terminate(); - assert(res.status == 200, `expected status code 200, got ${res.status}`); + assert(status == 200, `expected status code 200, got ${status}`); assert(data == "echo", `did not get response from echo server through WS tunnel, got ${data}`); }).timeout(120000); }); diff --git a/test/e2e/test_ssh.js b/test/e2e/test_ssh.js index cce5945..8385566 100644 --- a/test/e2e/test_ssh.js +++ b/test/e2e/test_ssh.js @@ -1,5 +1,6 @@ import assert from 'assert/strict'; import crypto from 'crypto'; +import http from 'node:http'; import { setTimeout } from 'timers/promises'; import { createAccount, createEchoServer, getAuthToken, getTunnel, putTunnel, sshClient } from './e2e-utils.js'; @@ -26,7 +27,7 @@ describe('SSH transport E2E', () => { after(async () => { process.env.NODE_ENV = "test"; - await terminator(undefined, {gracefulTimeout: 1000, drainTimeout: 500}); + await terminator(undefined, {gracefulTimeout: 1000, drainTimeout: 500}); await echoServerTerminator() }); @@ -54,7 +55,7 @@ describe('SSH transport E2E', () => { assert(res.status == 200, "could not create tunnel") res = await getTunnel(authToken, tunnelId); - let data = await res.json(); + let data = await res.json(); assert(data?.transport?.ssh?.enabled == true, "SSH transport not enabled"); assert(typeof data?.transport?.ssh?.url == 'string', "No SSH connect URL available"); @@ -66,32 +67,45 @@ describe('SSH transport E2E', () => { data?.transport?.ssh?.username, data?.transport?.ssh?.password, targetUrl, - ); + ); authToken = await getAuthToken(account.account_id); do { await setTimeout(1000); res = await getTunnel(authToken, tunnelId); - data = await res.json(); + data = await res.json(); } while (data?.connection?.connected == false); assert(data?.connection?.connected == true, "tunnel not connected"); const ingressUrl = new URL(data.ingress.http.url); - res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": ingressUrl.hostname - }, - body: "echo" - }) + let status; + ([status, data] = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": ingressUrl.hostname + } + }, (res) => { + let data = ''; - assert(res.status == 200, `expected status code 200, got ${res.status}`); - data = await res.text() - assert(data == "echo", `did not get response from echo server through WS tunnel, got ${data}`) + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve([res.statusCode, data])}); + }); + req.end('echo'); + })); terminateClient(); + assert(status == 200, `expected status code 200, got ${status}`); + assert(data == "echo", `did not get response from echo server through WS tunnel, got ${data}`); + }).timeout(60000); }); \ No newline at end of file diff --git a/test/e2e/test_ws.js b/test/e2e/test_ws.js index 8c16f44..648ca53 100644 --- a/test/e2e/test_ws.js +++ b/test/e2e/test_ws.js @@ -1,5 +1,6 @@ import assert from 'assert/strict'; import crypto from 'crypto'; +import http from 'node:http'; import { setTimeout } from 'timers/promises'; import { createAccount, createEchoServer, getAuthToken, getTunnel, putTunnel, startExposr } from './e2e-utils.js'; import { PGSQL_URL, REDIS_URL } from '../env.js'; @@ -59,22 +60,33 @@ describe('Websocket E2E', () => { assert(data?.connection?.connected == true, "tunnel not connected"); - const ingressUrl = new URL(data.ingress.http.url); + let status; + ([status, data] = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": ingressUrl.hostname + } + }, (res) => { + let data = ''; - res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": ingressUrl.hostname - }, - body: "echo" - }) + res.on('data', (chunk) => { + data += chunk; + }); - assert(res.status == 200, `expected status code 200, got ${res.status}`); - data = await res.text() - assert(data == "echo", `did not get response from echo server through WS tunnel, got ${data}`); + res.on('close', () => { resolve([res.statusCode, data])}); + }); + req.end('echo'); + })); exposrCliTerminator(); await terminator(undefined, {gracefulTimeout: 10000, drainTimeout: 500}); + + assert(status == 200, `expected status code 200, got ${status}`); + assert(data == "echo", `did not get response from echo server through WS tunnel, got ${data}`); }).timeout(60000); }); }); \ No newline at end of file diff --git a/test/system/ingress/test_http_ingress.js b/test/system/ingress/test_http_ingress.js index 9af9a38..c3c90e1 100644 --- a/test/system/ingress/test_http_ingress.js +++ b/test/system/ingress/test_http_ingress.js @@ -103,20 +103,34 @@ describe('http ingress', () => { }); }); - res = await fetch("http://127.0.0.1:10000", { - method: "GET", - headers: { - Host: `${tunnel.id}.localhost.example` - } + let [status, data] = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 10000, + method: 'GET', + path: '/', + headers: { + "Host": `${tunnel.id}.localhost.example` + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve([res.statusCode, data])}); + }); + req.end('echo'); }); - const data = await res.text(); - assert(data == "AA", `did not get expected reply, got ${data}`); - await client.destroy(); await transport.destroy(); await sockPair.terminate(); + assert(status == 200, `expected status code 200, got ${status}`); + assert(data == "AA", `did not get expected reply, got ${data}`); + }).timeout(2000); it(`http ingress can handle websocket upgrades`, async () => { diff --git a/test/unit/test-utils.ts b/test/unit/test-utils.ts index e8b89f3..08702bd 100644 --- a/test/unit/test-utils.ts +++ b/test/unit/test-utils.ts @@ -177,11 +177,16 @@ export const createEchoHttpServer = async (port: number = 20000, crtPath?: strin server.on('request', handleRequest); server.on('upgrade', handleUpgrade); - server.listen(port); + await new Promise((resolve) => { + server.listen(port, () => { + resolve(undefined); + }); + }); return { destroy: async () => { await new Promise((resolve) => { server.close(resolve); + server.closeAllConnections(); server.removeAllListeners('request'); server.removeAllListeners('upgrade'); }); diff --git a/test/unit/transport/test_ssh_transport.ts b/test/unit/transport/test_ssh_transport.ts index 6c05c98..8a488c4 100644 --- a/test/unit/transport/test_ssh_transport.ts +++ b/test/unit/transport/test_ssh_transport.ts @@ -1,6 +1,7 @@ import assert from 'assert/strict'; import crypto from 'crypto'; import net from 'net'; +import http from 'node:http'; import Config from '../../../src/config.js'; import TransportService from '../../../src/transport/transport-service.js' import { createEchoHttpServer, initStorageService } from '../test-utils.js'; @@ -149,28 +150,58 @@ describe('SSH transport', () => { }); }); - let res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${tunnel.id}.example.com` - }, - body: "echo" + do { + await clock.tickAsync(1000); + tunnel = await tunnelService.lookup(tunnelId); + } while (tunnel.state.connected == false); + + let {status, data}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end('echo'); }); - assert(res.status == 200, "did not get response from echo server"); - let data = await res.text(); + assert(status == 200, "did not get response from echo server"); assert(data == 'echo', "did not get response from echo server"); - res = await fetch("http://localhost:8080/file?size=1048576", { - method: 'GET', - headers: { - "Host": `${tunnel.id}.example.com` - }, + let {status: status2, data: data2}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'GET', + path: '/file?size=1048576', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end(); }); - assert(res.status == 200, "did not get response from echo server"); - let data2 = await res.blob(); - assert(data2.size == 1048576, "did not receive large file") + assert(status2 == 200, `did not get 200 response from echo server, got ${status2}`); + assert(data2.length == 1048576, "did not receive large file"); conn.destroy(); await transportService.destroy(); @@ -247,16 +278,32 @@ describe('SSH transport', () => { }); }); - let res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${tunnel.id}.example.com` - }, - body: "echo" - }); - assert(res.status == 200, "did not get response from echo server"); - let data = await res.text(); + do { + await clock.tickAsync(1000); + tunnel = await tunnelService.lookup(tunnelId); + } while (tunnel.state.connected == false); + + let {status, data}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end('echo'); + }); + assert(status == 200, `did not get 200 response from echo server, ${status}`); assert(data == 'echo', "did not get response from echo server"); conn.destroy(); @@ -352,29 +399,62 @@ describe('SSH transport', () => { transports = transportService.getTransports(tunnel2, "http://localhost"); const conn2 = await sshConn(transports.ssh!?.host, transports.ssh!?.port, transports.ssh!?.username, transports.ssh!?.password); - let res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${tunnel.id}.example.com` - }, - body: `${tunnel.id}` - }); - assert(res.status == 200, "did not get response from echo server"); - let data = await res.text(); + do { + await clock.tickAsync(1000); + tunnel = await tunnelService.lookup(tunnelId); + } while (tunnel.state.connected == false); - assert(data == tunnel.id, "did not get response from echo server"); + do { + await clock.tickAsync(1000); + tunnel2 = await tunnelService.lookup(tunnelId); + } while (tunnel2.state.connected == false); + + + let {status, data}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); - res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${tunnel2.id}.example.com` - }, - body: `${tunnel2.id}` + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end(tunnel.id); }); - assert(res.status == 200, "did not get response from echo server"); - data = await res.text(); + assert(status == 200, `did not get 200 response from echo server, ${status}`); + assert(data == tunnel.id, "did not get response from echo server"); - assert(data == tunnel2.id, "did not get response from echo server"); + let {status: status2, data: data2}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end(tunnel2.id); + }); + assert(status2 == 200, `did not get 200 response from echo server, ${status}`); + assert(data2 == tunnel2.id, "did not get response from echo server"); conn.destroy(); let clients: any; @@ -466,26 +546,33 @@ describe('SSH transport', () => { }); await readyWait; - let res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${tunnel.id}.example.com` - }, - body: "echo" - }); - - assert(res.status == 200, "did not get response from echo server"); - let data = await res.text(); - assert(data == 'echo', "did not get response from echo server"); + do { + await clock.tickAsync(1000); + tunnel = await tunnelService.lookup(tunnelId); + } while (tunnel.state.connected == false); + + let {status, data}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); - res = await fetch("http://localhost:8080/file?size=1048576", { - method: 'GET', - headers: { - "Host": `${tunnel.id}.example.com` - }, + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end('echo'); }); - assert(res.status == 200, "did not get response from echo server"); + assert(status == 200, `did not get 200 response from echo server, ${status}`); + assert(data == 'echo', "did not get response from echo server"); conn.destroy(); await transportService.destroy(); diff --git a/test/unit/transport/test_ws_transport.ts b/test/unit/transport/test_ws_transport.ts index 5f21499..e8fabda 100644 --- a/test/unit/transport/test_ws_transport.ts +++ b/test/unit/transport/test_ws_transport.ts @@ -1,6 +1,7 @@ import assert from 'assert/strict'; import crypto from 'crypto'; import net from 'net'; +import http from 'node:http'; import WebSocket from 'ws'; import Config from '../../../src/config.js'; import TransportService from '../../../src/transport/transport-service.js' @@ -137,30 +138,60 @@ describe('WS transport', () => { }); }); - let res = await fetch("http://localhost:8080", { - method: 'POST', - headers: { - "Host": `${tunnel.id}.example.com` - }, - body: "echo" + do { + await clock.tickAsync(1000); + tunnel = await tunnelService.lookup(tunnelId); + } while (tunnel.state.connected == false); + + let {status, data}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'POST', + path: '/', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end('echo'); }); - assert(res.status == 200, `did not get 200 response from echo server, ${res.status}`); - let data = await res.text(); + assert(status == 200, `did not get 200 response from echo server, ${status}`); assert(data == 'echo', "did not get response from echo server"); - res = await fetch("http://localhost:8080/file?size=1048576", { - method: 'GET', - headers: { - "Host": `${tunnel.id}.example.com` - }, - }); - assert(res.status == 200, `did not get 200 response from echo server, ${res.status}`); + let {status: status2, data: data2}: {status: number | undefined, data: any} = await new Promise((resolve) => { + const req = http.request({ + hostname: 'localhost', + port: 8080, + method: 'GET', + path: '/file?size=1048576', + headers: { + "Host": `${tunnel.id}.example.com` + } + }, (res) => { + let data = ''; - let data2 = await res.blob(); - assert(data2.size == 1048576, "did not receive large file") + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('close', () => { resolve({status: res.statusCode, data})}); + }); + req.end(); + }); ws.close(); await transportService.destroy(); + + assert(status2 == 200, `did not get 200 response from echo server, got ${status2}`); + assert(data2.length == 1048576, "did not receive large file"); }); }); \ No newline at end of file