Refactor VPN proxy client implementation
Build and Deploy Gateway / build-and-push (push) Failing after 2s
Build and Deploy Gateway / deploy (push) Has been skipped

This commit is contained in:
2026-08-09 00:41:52 +03:00
parent 32be4380a3
commit 34d8b681ad
190 changed files with 20064 additions and 9761 deletions
+175 -1
View File
@@ -1,9 +1,42 @@
import assert from "node:assert/strict";
import { readFileSync } from 'node:fs';
import test from "node:test";
const {
buildSharedProxyInfo,
} = await import("../../src/server/sharedProxy.js");
} = await import("../../dist/server/sharedProxy.js");
const {
createSharedProxyRoute,
} = await import('../../dist/server/http/routes/sharedProxyRoute.js');
function response() {
return {
writeHead(status, headers) {
this.status = status;
this.headers = headers;
},
end(payload) {
this.rawPayload = payload;
this.payload = JSON.parse(payload);
},
};
}
function createHarness(overrides = {}) {
const events = [];
const refreshError = overrides.refreshError;
const route = createSharedProxyRoute({
appMode: overrides.appMode ?? 'gateway',
proxyPort: overrides.proxyPort ?? 8080,
sharedProxyHost: overrides.sharedProxyHost ?? '',
refreshRuntime: async () => {
events.push('refresh');
if (refreshError) throw refreshError;
return { running: overrides.running ?? true };
},
});
return { events, route };
}
test("gateway shared proxy info exposes host and socks proxy when running", () => {
const info = buildSharedProxyInfo({
@@ -22,3 +55,144 @@ test("gateway shared proxy info exposes host and socks proxy when running", () =
socksUrl: "socks5://192.168.50.111:8080",
});
});
test('shared proxy builder preserves host precedence, IPv6, port and unavailable quirks', () => {
const configured = buildSharedProxyInfo({
appMode: 'gateway',
proxyPort: '9000px',
running: true,
hostHeader: 'ignored.example:1234',
sharedProxyHost: ' proxy.example ',
});
assert.deepEqual(configured.proxy, {
host: 'proxy.example',
port: 9000,
protocol: 'socks5',
httpUrl: 'http://proxy.example:9000',
socksUrl: 'socks5://proxy.example:9000',
});
const ipv6 = buildSharedProxyInfo({
appMode: 'gateway',
proxyPort: 1080,
running: true,
hostHeader: '[2001:db8::1]:443',
});
assert.equal(ipv6.proxy.host, '2001:db8::1');
assert.equal(buildSharedProxyInfo({
appMode: 'gateway',
proxyPort: 1080,
running: true,
hostHeader: '',
}).available, '');
assert.equal(buildSharedProxyInfo({
appMode: 'gateway',
proxyPort: 65536,
running: true,
hostHeader: 'gateway.local',
}).available, false);
});
test('shared proxy route refreshes once before reading Host and preserves raw JSON response', async () => {
const harness = createHarness();
const res = response();
const headers = {};
Object.defineProperty(headers, 'host', {
get() {
harness.events.push('host');
return '192.168.50.111:3456';
},
});
assert.equal(await harness.route.handle({
method: 'GET',
url: '/api/shared-proxy',
headers,
}, res), true);
assert.deepEqual(harness.events, ['refresh', 'host']);
assert.equal(res.status, 200);
assert.equal(res.headers['content-type'], 'application/json; charset=utf-8');
assert.equal(res.rawPayload.endsWith('\n'), false);
assert.deepEqual(res.payload, {
success: true,
available: true,
mode: 'gateway',
proxy: {
host: '192.168.50.111',
port: 8080,
protocol: 'socks5',
httpUrl: 'http://192.168.50.111:8080',
socksUrl: 'socks5://192.168.50.111:8080',
},
});
});
test('shared proxy route still refreshes for client and unavailable responses', async () => {
for (const options of [
{ appMode: 'client' },
{ running: false },
{ proxyPort: 'invalid' },
]) {
const harness = createHarness(options);
const res = response();
await harness.route.handle({
method: 'GET',
url: '/api/shared-proxy',
headers: { host: 'gateway.local' },
}, res);
assert.deepEqual(harness.events, ['refresh']);
assert.equal(res.payload.available, false);
assert.equal(res.payload.proxy, null);
}
});
test('shared proxy route exact guard avoids refresh and errors propagate unchanged', async () => {
for (const [method, url] of [
['POST', '/api/shared-proxy'],
['GET', '/api/shared-proxy?source=client'],
['GET', '/api/shared-proxy/'],
['GET', '/api/other'],
]) {
const harness = createHarness();
assert.equal(await harness.route.handle({ method, url, headers: {} }, response()), false);
assert.deepEqual(harness.events, []);
}
const refreshError = new Error('refresh failed');
const brokenRefresh = createHarness({ refreshError });
await assert.rejects(
brokenRefresh.route.handle({
method: 'GET',
url: '/api/shared-proxy',
headers: { host: 'gateway.local' },
}, response()),
(error) => error === refreshError,
);
const builderError = new Error('builder failed');
const brokenBuilder = createHarness({
sharedProxyHost: { toString() { throw builderError; } },
});
await assert.rejects(
brokenBuilder.route.handle({
method: 'GET',
url: '/api/shared-proxy',
headers: { host: 'gateway.local' },
}, response()),
(error) => error === builderError,
);
});
test('shared proxy route is the sole HTTP owner', () => {
const index = readFileSync(new URL('../../src/server/index.ts', import.meta.url), 'utf8');
const route = readFileSync(
new URL('../../src/server/http/routes/sharedProxyRoute.ts', import.meta.url),
'utf8',
);
assert.match(index, /createSharedProxyRoute\(\{/);
assert.match(index, /sharedProxyRoute\.handle\(req, res\)/);
assert.doesNotMatch(index, /['"]\/api\/shared-proxy['"]|buildSharedProxyInfo/);
assert.match(route, /req\.url !== '\/api\/shared-proxy'/);
assert.match(route, /buildSharedProxyInfo\(\{/);
});