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); });