127 lines
5.2 KiB
JavaScript
127 lines
5.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
process.env.APP_MODE = 'gateway';
|
|
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'vpn-proxy-gateway-test-'));
|
|
process.env.SING_BOX_CACHE = path.join(process.env.DATA_DIR, 'cache.db');
|
|
|
|
const {
|
|
buildDualChannelGatewayConfig,
|
|
buildGatewayConfig,
|
|
dualChannelConfigMatchesApplied,
|
|
fingerprintConfiguredOutbound,
|
|
fingerprintSelectedOutbound,
|
|
} = await import(`../../dist/server/singbox.js?gateway=${Date.now()}`);
|
|
|
|
const subscriptionConfig = {
|
|
outbounds: [{
|
|
type: 'vless',
|
|
tag: 'test-vpn',
|
|
server: 'vpn.example.test',
|
|
server_port: 443,
|
|
uuid: '00000000-0000-4000-8000-000000000000',
|
|
tls: { enabled: true },
|
|
}],
|
|
};
|
|
|
|
test('gateway preserves mixed user-rule order and dynamic VPN target', () => {
|
|
const config = buildGatewayConfig(subscriptionConfig, 'test-vpn', {
|
|
routeRules: [
|
|
{ type: 'domain', value: 'api.example.com', enabled: true, outbound: 'vpn' },
|
|
{ type: 'domain_suffix', value: 'example.com', enabled: true, outbound: 'direct' },
|
|
{ type: 'domain_keyword', value: 'disabled', enabled: false, outbound: 'vpn' },
|
|
],
|
|
});
|
|
|
|
assert.deepEqual(config.route.rule_set, []);
|
|
assert.deepEqual(config.inbounds.map((inbound) => inbound.tag), [
|
|
'tproxy-in',
|
|
'mixed-in',
|
|
'diagnostics-vpn-in',
|
|
]);
|
|
assert.deepEqual(config.inbounds[2], {
|
|
type: 'mixed',
|
|
tag: 'diagnostics-vpn-in',
|
|
listen: '127.0.0.1',
|
|
listen_port: 18080,
|
|
set_system_proxy: false,
|
|
});
|
|
assert.deepEqual(config.route.rules, [
|
|
{
|
|
inbound: ['tproxy-in', 'mixed-in', 'diagnostics-vpn-in'],
|
|
action: 'sniff',
|
|
sniffer: ['http', 'tls', 'quic'],
|
|
timeout: '1s',
|
|
},
|
|
{ inbound: ['diagnostics-vpn-in'], outbound: 'test-vpn' },
|
|
{ domain: ['api.example.com'], outbound: 'test-vpn' },
|
|
{ domain_suffix: ['example.com'], outbound: 'direct' },
|
|
{ inbound: ['tproxy-in'], outbound: 'test-vpn' },
|
|
{ inbound: ['mixed-in'], outbound: 'test-vpn' },
|
|
]);
|
|
assert.deepEqual(config.experimental.clash_api, { external_controller: '127.0.0.1:19090' });
|
|
assert.equal(config.route.final, 'test-vpn');
|
|
});
|
|
|
|
test('gateway dual-channel config fixes probes to role tags and keeps inbound connections', () => {
|
|
const reserveConfig = structuredClone(subscriptionConfig);
|
|
reserveConfig.outbounds[0].tag = 'same-provider-tag';
|
|
const primaryConfig = structuredClone(subscriptionConfig);
|
|
primaryConfig.outbounds[0].tag = 'same-provider-tag';
|
|
const config = buildDualChannelGatewayConfig({
|
|
primary: { subscriptionConfig: primaryConfig, selectedServerId: 'same-provider-tag' },
|
|
reserve: { subscriptionConfig: reserveConfig, selectedServerId: 'same-provider-tag' },
|
|
}, {
|
|
routeRules: [{ type: 'domain', value: 'api.example.com', enabled: true, outbound: 'vpn' }],
|
|
});
|
|
|
|
assert.deepEqual(config.outbounds.map(({ tag }) => tag), [
|
|
'channel-primary', 'channel-reserve', 'channel-selector', 'direct',
|
|
]);
|
|
assert.deepEqual(config.outbounds[2], {
|
|
type: 'selector',
|
|
tag: 'channel-selector',
|
|
outbounds: ['channel-primary', 'channel-reserve'],
|
|
default: 'channel-primary',
|
|
interrupt_exist_connections: false,
|
|
});
|
|
assert.deepEqual(config.route.rules.slice(1, 5), [
|
|
{ inbound: ['diagnostics-primary-in'], outbound: 'channel-primary' },
|
|
{ inbound: ['diagnostics-reserve-in'], outbound: 'channel-reserve' },
|
|
{ inbound: ['diagnostics-vpn-in'], outbound: 'channel-selector' },
|
|
{ domain: ['api.example.com'], outbound: 'channel-selector' },
|
|
]);
|
|
assert.equal(config.route.final, 'channel-selector');
|
|
const restoredReserve = buildDualChannelGatewayConfig({
|
|
primary: { subscriptionConfig: primaryConfig, selectedServerId: 'same-provider-tag' },
|
|
reserve: { subscriptionConfig: reserveConfig, selectedServerId: 'same-provider-tag' },
|
|
}, { defaultRole: 'reserve' });
|
|
assert.equal(restoredReserve.outbounds[2].default, 'channel-reserve');
|
|
});
|
|
|
|
test('cached dual-channel outbounds must match the applied provider fingerprints', () => {
|
|
const config = buildDualChannelGatewayConfig({
|
|
primary: { subscriptionConfig, selectedServerId: 'test-vpn' },
|
|
reserve: { subscriptionConfig, selectedServerId: 'test-vpn' },
|
|
});
|
|
const expected = fingerprintSelectedOutbound(subscriptionConfig, 'test-vpn');
|
|
const applied = {
|
|
primary: { profileId: 'primary', serverId: 'test-vpn' },
|
|
reserve: { profileId: 'reserve', serverId: 'test-vpn' },
|
|
primaryConfigFingerprint: expected,
|
|
reserveConfigFingerprint: expected,
|
|
};
|
|
assert.equal(dualChannelConfigMatchesApplied(config, applied, 'primary'), true);
|
|
assert.equal(dualChannelConfigMatchesApplied(config, applied, 'reserve'), false);
|
|
assert.equal(fingerprintConfiguredOutbound(config.outbounds[0], 'test-vpn'), expected);
|
|
config.outbounds[0].uuid = '11111111-1111-4111-8111-111111111111';
|
|
assert.notEqual(fingerprintConfiguredOutbound(config.outbounds[0], 'test-vpn'), expected);
|
|
assert.equal(dualChannelConfigMatchesApplied(config, applied, 'primary'), false);
|
|
config.outbounds[0].uuid = subscriptionConfig.outbounds[0].uuid;
|
|
config.outbounds[2].outbounds = ['channel-primary'];
|
|
assert.equal(dualChannelConfigMatchesApplied(config, applied, 'primary'), false);
|
|
});
|