AikidoSec-safe-chain/packages/safe-chain/src/registryProxy/registryProxy.http-proxy.spec.js
2025-10-22 15:41:33 +02:00

225 lines
6 KiB
JavaScript

import { before, after, describe, it } from "node:test";
import assert from "node:assert";
import http from "http";
import {
createSafeChainProxy,
mergeSafeChainProxyEnvironmentVariables,
} from "./registryProxy.js";
describe("registryProxy.httpProxy", () => {
let proxy, proxyHost, proxyPort;
let testHttpServer, testHttpServerPort;
before(async () => {
// Start safe-chain proxy
proxy = createSafeChainProxy();
await proxy.startServer();
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
const proxyUrl = new URL(envVars.HTTPS_PROXY);
proxyHost = proxyUrl.hostname;
proxyPort = parseInt(proxyUrl.port, 10);
// Start a test HTTP server to forward requests to
testHttpServer = http.createServer((req, res) => {
if (req.url === "/test") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("HTTP test response");
} else if (req.url === "/echo-headers") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(req.headers));
} else if (req.url === "/echo-method") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(req.method);
} else if (req.url === "/post-echo") {
let body = "";
req.on("data", (chunk) => {
body += chunk.toString();
});
req.on("end", () => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(body);
});
} else if (req.url === "/404") {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
} else {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("OK");
}
});
testHttpServerPort = await new Promise((resolve) => {
testHttpServer.listen(0, () => {
resolve(testHttpServer.address().port);
});
});
});
after(async () => {
await proxy.stopServer();
await new Promise((resolve) => {
testHttpServer.close(() => resolve());
setTimeout(resolve, 1000);
});
});
it("should forward HTTP GET requests", async () => {
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
`http://localhost:${testHttpServerPort}/test`,
"GET"
);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.body, "HTTP test response");
});
it("should forward HTTP POST requests with body", async () => {
const postData = "test post data";
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
`http://localhost:${testHttpServerPort}/post-echo`,
"POST",
postData
);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.body, postData);
});
it("should preserve request headers", async () => {
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
`http://localhost:${testHttpServerPort}/echo-headers`,
"GET",
null,
{
"X-Custom-Header": "test-value",
"User-Agent": "test-agent/1.0",
}
);
assert.strictEqual(response.statusCode, 200);
const headers = JSON.parse(response.body);
assert.strictEqual(headers["x-custom-header"], "test-value");
assert.strictEqual(headers["user-agent"], "test-agent/1.0");
});
it("should preserve HTTP methods", async () => {
const methods = ["GET", "POST", "PUT", "DELETE"];
for (const method of methods) {
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
`http://localhost:${testHttpServerPort}/echo-method`,
method
);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(response.body, method);
}
});
it("should forward 404 responses correctly", async () => {
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
`http://localhost:${testHttpServerPort}/404`,
"GET"
);
assert.strictEqual(response.statusCode, 404);
assert.strictEqual(response.body, "Not Found");
});
it("should handle invalid host with 502 Bad Gateway", async () => {
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
"http://invalid-host-that-does-not-exist.test:9999/test",
"GET"
);
assert.strictEqual(response.statusCode, 502);
assert.ok(response.body.includes("Bad Gateway"));
});
it("should handle HTTPS URLs sent to HTTP proxy", async () => {
// Some clients incorrectly send https:// URLs to the HTTP proxy handler
// instead of using CONNECT. The proxy should handle this gracefully.
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
"https://registry.npmjs.org/lodash",
"GET"
);
// Should successfully forward the HTTPS request
assert.strictEqual(response.statusCode, 200);
assert.ok(response.body.includes("lodash"));
});
it("should handle unsupported protocols with 502", async () => {
const response = await makeHttpProxyRequest(
proxyHost,
proxyPort,
"ftp://example.com/file.txt",
"GET"
);
assert.strictEqual(response.statusCode, 502);
assert.ok(response.body.includes("Unsupported protocol"));
});
});
function makeHttpProxyRequest(
proxyHost,
proxyPort,
targetUrl,
method = "GET",
body = null,
extraHeaders = {}
) {
return new Promise((resolve, reject) => {
const options = {
hostname: proxyHost,
port: proxyPort,
path: targetUrl,
method: method,
headers: {
Host: new URL(targetUrl).host,
...extraHeaders,
},
};
const req = http.request(options, (res) => {
let responseBody = "";
res.on("data", (chunk) => {
responseBody += chunk.toString();
});
res.on("end", () => {
resolve({
statusCode: res.statusCode,
headers: res.headers,
body: responseBody,
});
});
});
req.on("error", (err) => {
reject(err);
});
if (body) {
req.write(body);
}
req.end();
});
}