Files
3x-ui/frontend/src/test/outbound-defaults.test.ts
T
Rouzbeh† b07fad0e69 refactor(wireguard): drop removed workers field (xray v26.6.22) (#5509)
* v3.4.0

* refactor(wireguard): drop removed `workers` field (xray v26.6.22)

xray-core v26.6.22 (PR #6287) removed the WireGuard `workers` (num_workers)
config field; the engine now relies on wireguard-go's internal worker
fallback and no longer reads it. Remove it from the panel so it stops
emitting a key xray ignores.

Removed from the inbound/outbound/outbound-form WireGuard schemas, both
WireGuard forms, the outbound form adapter (both directions) and defaults,
the two affected tests, and the `workers` label in all 13 locales. Existing
configs that still carry workers are simply dropped on parse — no migration
needed since the field had no runtime effect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Update version

---------

Co-authored-by: MHSanaei <ho3ein.sanaei@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:23:02 +02:00

231 lines
7.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
createDefaultBlackholeOutboundSettings,
createDefaultDNSOutboundSettings,
createDefaultFreedomOutboundSettings,
createDefaultHttpOutboundSettings,
createDefaultHysteriaOutboundSettings,
createDefaultLoopbackOutboundSettings,
createDefaultShadowsocksOutboundSettings,
createDefaultSocksOutboundSettings,
createDefaultTrojanOutboundSettings,
createDefaultVlessOutboundSettings,
createDefaultVmessOutboundSettings,
createDefaultWireguardOutboundSettings,
createDefaultOutboundSettings,
} from '@/lib/xray/outbound-defaults';
import {
BlackholeOutboundSettingsSchema,
DNSOutboundSettingsSchema,
FreedomOutboundSettingsSchema,
HttpOutboundSettingsSchema,
HysteriaOutboundSettingsSchema,
LoopbackOutboundSettingsSchema,
ShadowsocksOutboundSettingsSchema,
SocksOutboundSettingsSchema,
TrojanOutboundSettingsSchema,
VlessOutboundSettingsSchema,
VmessOutboundSettingsSchema,
WireguardOutboundSettingsSchema,
} from '@/schemas/protocols/outbound';
// Snapshot + Zod round-trip for each createDefault*OutboundSettings factory.
// The factory output mirrors the legacy `new Outbound.<X>Settings()` start
// state, so most required fields are empty stubs (address, port, password,
// id). Zod parsing happens AFTER patching the stubs with sensible values —
// this catches schema/factory drift without forcing the factory to invent
// data it shouldn't.
const SAMPLE_ID = '11111111-2222-4333-8444-555555555555';
const SAMPLE_ADDRESS = '1.2.3.4';
const SAMPLE_PORT = 443;
const SAMPLE_SECRET = 'abc123def456ghi789';
describe('outbound default factories: shape snapshots', () => {
it('freedom is the empty object', () => {
expect(createDefaultFreedomOutboundSettings()).toEqual({});
});
it('blackhole is the empty object', () => {
expect(createDefaultBlackholeOutboundSettings()).toEqual({});
});
it('loopback has an empty inboundTag', () => {
expect(createDefaultLoopbackOutboundSettings()).toEqual({ inboundTag: '' });
});
it('dns has the legacy constructor defaults', () => {
expect(createDefaultDNSOutboundSettings()).toEqual({
rewriteNetwork: '',
rewriteAddress: '',
rewritePort: 53,
userLevel: 0,
rules: [],
});
});
it('vmess wraps a single vnext server with one user', () => {
expect(createDefaultVmessOutboundSettings()).toEqual({
vnext: [{ address: '', port: 443, users: [{ id: '', security: 'auto' }] }],
});
});
it('vless lays the connect target flat', () => {
expect(createDefaultVlessOutboundSettings()).toEqual({
address: '',
port: 443,
id: '',
flow: '',
encryption: 'none',
});
});
it('trojan wraps a single server', () => {
expect(createDefaultTrojanOutboundSettings()).toEqual({
servers: [{ address: '', port: 443, password: '' }],
});
});
it('shadowsocks defaults to 2022-blake3-aes-128-gcm', () => {
expect(createDefaultShadowsocksOutboundSettings()).toEqual({
servers: [{
address: '', port: 443, password: '', method: '2022-blake3-aes-128-gcm',
}],
});
});
it('socks defaults to port 1080 with no users', () => {
expect(createDefaultSocksOutboundSettings()).toEqual({
servers: [{ address: '', port: 1080, users: [] }],
});
});
it('http defaults to port 8080 with no users', () => {
expect(createDefaultHttpOutboundSettings()).toEqual({
servers: [{ address: '', port: 8080, users: [] }],
});
});
it('wireguard seeds secretKey deterministically when given', () => {
const out = createDefaultWireguardOutboundSettings({ secretKey: SAMPLE_SECRET });
expect(out.secretKey).toBe(SAMPLE_SECRET);
expect(out.mtu).toBe(1420);
expect(out.address).toEqual([]);
expect(out.noKernelTun).toBe(false);
expect(out.peers).toEqual([{
publicKey: '', allowedIPs: ['0.0.0.0/0', '::/0'], endpoint: '',
}]);
});
it('wireguard generates a secretKey when none is seeded', () => {
const out = createDefaultWireguardOutboundSettings();
expect(out.secretKey).toMatch(/^[A-Za-z0-9+/=]+$/);
expect(out.secretKey.length).toBeGreaterThan(8);
});
it('hysteria defaults to port 443 version 2', () => {
expect(createDefaultHysteriaOutboundSettings()).toEqual({
address: '', port: 443, version: 2,
});
});
});
describe('outbound default factories: schema acceptance after stub fill-in', () => {
it('freedom default parses (no required fields)', () => {
expect(FreedomOutboundSettingsSchema.safeParse(
createDefaultFreedomOutboundSettings(),
).success).toBe(true);
});
it('blackhole default parses (no required fields)', () => {
expect(BlackholeOutboundSettingsSchema.safeParse(
createDefaultBlackholeOutboundSettings(),
).success).toBe(true);
});
it('loopback default parses (no required fields)', () => {
expect(LoopbackOutboundSettingsSchema.safeParse(
createDefaultLoopbackOutboundSettings(),
).success).toBe(true);
});
it('dns default parses', () => {
expect(DNSOutboundSettingsSchema.safeParse(
createDefaultDNSOutboundSettings(),
).success).toBe(true);
});
it('vmess parses once vnext fields are filled', () => {
const def = createDefaultVmessOutboundSettings();
def.vnext[0].address = SAMPLE_ADDRESS;
def.vnext[0].port = SAMPLE_PORT;
def.vnext[0].users[0].id = SAMPLE_ID;
expect(VmessOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('vless parses once address/port/id are filled', () => {
const def = createDefaultVlessOutboundSettings();
def.address = SAMPLE_ADDRESS;
def.port = SAMPLE_PORT;
def.id = SAMPLE_ID;
expect(VlessOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('trojan parses once server fields are filled', () => {
const def = createDefaultTrojanOutboundSettings();
def.servers[0].address = SAMPLE_ADDRESS;
def.servers[0].password = 'secret';
expect(TrojanOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('shadowsocks parses once server fields are filled', () => {
const def = createDefaultShadowsocksOutboundSettings();
def.servers[0].address = SAMPLE_ADDRESS;
def.servers[0].password = 'secret';
expect(ShadowsocksOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('socks parses once address is filled', () => {
const def = createDefaultSocksOutboundSettings();
def.servers[0].address = SAMPLE_ADDRESS;
expect(SocksOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('http parses once address is filled', () => {
const def = createDefaultHttpOutboundSettings();
def.servers[0].address = SAMPLE_ADDRESS;
expect(HttpOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('wireguard parses once peer + secretKey are filled', () => {
const def = createDefaultWireguardOutboundSettings({ secretKey: SAMPLE_SECRET });
def.peers[0].publicKey = 'pk';
def.peers[0].endpoint = `${SAMPLE_ADDRESS}:51820`;
expect(WireguardOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
it('hysteria parses once address is filled', () => {
const def = createDefaultHysteriaOutboundSettings();
def.address = SAMPLE_ADDRESS;
expect(HysteriaOutboundSettingsSchema.safeParse(def).success).toBe(true);
});
});
describe('createDefaultOutboundSettings dispatcher', () => {
const PROTOCOLS = [
'freedom', 'blackhole', 'dns', 'vmess', 'vless', 'trojan', 'shadowsocks',
'socks', 'http', 'wireguard', 'hysteria', 'loopback',
];
for (const protocol of PROTOCOLS) {
it(`returns non-null for ${protocol}`, () => {
expect(createDefaultOutboundSettings(protocol)).not.toBeNull();
});
}
it('returns null for an unknown protocol', () => {
expect(createDefaultOutboundSettings('mysterious')).toBeNull();
});
});