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