outbound-defaults.test.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { describe, expect, it } from 'vitest';
  2. import {
  3. createDefaultBlackholeOutboundSettings,
  4. createDefaultDNSOutboundSettings,
  5. createDefaultFreedomOutboundSettings,
  6. createDefaultHttpOutboundSettings,
  7. createDefaultHysteriaOutboundSettings,
  8. createDefaultLoopbackOutboundSettings,
  9. createDefaultShadowsocksOutboundSettings,
  10. createDefaultSocksOutboundSettings,
  11. createDefaultTrojanOutboundSettings,
  12. createDefaultVlessOutboundSettings,
  13. createDefaultVmessOutboundSettings,
  14. createDefaultWireguardOutboundSettings,
  15. createDefaultOutboundSettings,
  16. } from '@/lib/xray/outbound-defaults';
  17. import {
  18. BlackholeOutboundSettingsSchema,
  19. DNSOutboundSettingsSchema,
  20. FreedomOutboundSettingsSchema,
  21. HttpOutboundSettingsSchema,
  22. HysteriaOutboundSettingsSchema,
  23. LoopbackOutboundSettingsSchema,
  24. ShadowsocksOutboundSettingsSchema,
  25. SocksOutboundSettingsSchema,
  26. TrojanOutboundSettingsSchema,
  27. VlessOutboundSettingsSchema,
  28. VmessOutboundSettingsSchema,
  29. WireguardOutboundSettingsSchema,
  30. } from '@/schemas/protocols/outbound';
  31. // Snapshot + Zod round-trip for each createDefault*OutboundSettings factory.
  32. // The factory output mirrors the legacy `new Outbound.<X>Settings()` start
  33. // state, so most required fields are empty stubs (address, port, password,
  34. // id). Zod parsing happens AFTER patching the stubs with sensible values —
  35. // this catches schema/factory drift without forcing the factory to invent
  36. // data it shouldn't.
  37. const SAMPLE_ID = '11111111-2222-4333-8444-555555555555';
  38. const SAMPLE_ADDRESS = '1.2.3.4';
  39. const SAMPLE_PORT = 443;
  40. const SAMPLE_SECRET = 'abc123def456ghi789';
  41. describe('outbound default factories: shape snapshots', () => {
  42. it('freedom is the empty object', () => {
  43. expect(createDefaultFreedomOutboundSettings()).toEqual({});
  44. });
  45. it('blackhole is the empty object', () => {
  46. expect(createDefaultBlackholeOutboundSettings()).toEqual({});
  47. });
  48. it('loopback has an empty inboundTag', () => {
  49. expect(createDefaultLoopbackOutboundSettings()).toEqual({ inboundTag: '' });
  50. });
  51. it('dns has the legacy constructor defaults', () => {
  52. expect(createDefaultDNSOutboundSettings()).toEqual({
  53. rewriteNetwork: '',
  54. rewriteAddress: '',
  55. rewritePort: 53,
  56. userLevel: 0,
  57. rules: [],
  58. });
  59. });
  60. it('vmess wraps a single vnext server with one user', () => {
  61. expect(createDefaultVmessOutboundSettings()).toEqual({
  62. vnext: [{ address: '', port: 443, users: [{ id: '', security: 'auto' }] }],
  63. });
  64. });
  65. it('vless lays the connect target flat', () => {
  66. expect(createDefaultVlessOutboundSettings()).toEqual({
  67. address: '',
  68. port: 443,
  69. id: '',
  70. flow: '',
  71. encryption: 'none',
  72. });
  73. });
  74. it('trojan wraps a single server', () => {
  75. expect(createDefaultTrojanOutboundSettings()).toEqual({
  76. servers: [{ address: '', port: 443, password: '' }],
  77. });
  78. });
  79. it('shadowsocks defaults to 2022-blake3-aes-128-gcm', () => {
  80. expect(createDefaultShadowsocksOutboundSettings()).toEqual({
  81. servers: [
  82. {
  83. address: '',
  84. port: 443,
  85. password: '',
  86. method: '2022-blake3-aes-128-gcm',
  87. },
  88. ],
  89. });
  90. });
  91. it('socks defaults to port 1080 with no users', () => {
  92. expect(createDefaultSocksOutboundSettings()).toEqual({
  93. servers: [{ address: '', port: 1080, users: [] }],
  94. });
  95. });
  96. it('http defaults to port 8080 with no users', () => {
  97. expect(createDefaultHttpOutboundSettings()).toEqual({
  98. servers: [{ address: '', port: 8080, users: [] }],
  99. });
  100. });
  101. it('wireguard seeds secretKey deterministically when given', () => {
  102. const out = createDefaultWireguardOutboundSettings({ secretKey: SAMPLE_SECRET });
  103. expect(out.secretKey).toBe(SAMPLE_SECRET);
  104. expect(out.mtu).toBe(1420);
  105. expect(out.address).toEqual([]);
  106. expect(out.noKernelTun).toBe(false);
  107. expect(out.peers).toEqual([
  108. {
  109. publicKey: '',
  110. allowedIPs: ['0.0.0.0/0', '::/0'],
  111. endpoint: '',
  112. },
  113. ]);
  114. });
  115. it('wireguard generates a secretKey when none is seeded', () => {
  116. const out = createDefaultWireguardOutboundSettings();
  117. expect(out.secretKey).toMatch(/^[A-Za-z0-9+/=]+$/);
  118. expect(out.secretKey.length).toBeGreaterThan(8);
  119. });
  120. it('hysteria defaults to port 443 version 2', () => {
  121. expect(createDefaultHysteriaOutboundSettings()).toEqual({
  122. address: '',
  123. port: 443,
  124. version: 2,
  125. });
  126. });
  127. });
  128. describe('outbound default factories: schema acceptance after stub fill-in', () => {
  129. it('freedom default parses (no required fields)', () => {
  130. expect(
  131. FreedomOutboundSettingsSchema.safeParse(createDefaultFreedomOutboundSettings()).success,
  132. ).toBe(true);
  133. });
  134. it('blackhole default parses (no required fields)', () => {
  135. expect(
  136. BlackholeOutboundSettingsSchema.safeParse(createDefaultBlackholeOutboundSettings()).success,
  137. ).toBe(true);
  138. });
  139. it('loopback default parses (no required fields)', () => {
  140. expect(
  141. LoopbackOutboundSettingsSchema.safeParse(createDefaultLoopbackOutboundSettings()).success,
  142. ).toBe(true);
  143. });
  144. it('dns default parses', () => {
  145. expect(DNSOutboundSettingsSchema.safeParse(createDefaultDNSOutboundSettings()).success).toBe(
  146. true,
  147. );
  148. });
  149. it('vmess parses once vnext fields are filled', () => {
  150. const def = createDefaultVmessOutboundSettings();
  151. def.vnext[0].address = SAMPLE_ADDRESS;
  152. def.vnext[0].port = SAMPLE_PORT;
  153. def.vnext[0].users[0].id = SAMPLE_ID;
  154. expect(VmessOutboundSettingsSchema.safeParse(def).success).toBe(true);
  155. });
  156. it('vless parses once address/port/id are filled', () => {
  157. const def = createDefaultVlessOutboundSettings();
  158. def.address = SAMPLE_ADDRESS;
  159. def.port = SAMPLE_PORT;
  160. def.id = SAMPLE_ID;
  161. expect(VlessOutboundSettingsSchema.safeParse(def).success).toBe(true);
  162. });
  163. it('trojan parses once server fields are filled', () => {
  164. const def = createDefaultTrojanOutboundSettings();
  165. def.servers[0].address = SAMPLE_ADDRESS;
  166. def.servers[0].password = 'secret';
  167. expect(TrojanOutboundSettingsSchema.safeParse(def).success).toBe(true);
  168. });
  169. it('shadowsocks parses once server fields are filled', () => {
  170. const def = createDefaultShadowsocksOutboundSettings();
  171. def.servers[0].address = SAMPLE_ADDRESS;
  172. def.servers[0].password = 'secret';
  173. expect(ShadowsocksOutboundSettingsSchema.safeParse(def).success).toBe(true);
  174. });
  175. it('socks parses once address is filled', () => {
  176. const def = createDefaultSocksOutboundSettings();
  177. def.servers[0].address = SAMPLE_ADDRESS;
  178. expect(SocksOutboundSettingsSchema.safeParse(def).success).toBe(true);
  179. });
  180. it('http parses once address is filled', () => {
  181. const def = createDefaultHttpOutboundSettings();
  182. def.servers[0].address = SAMPLE_ADDRESS;
  183. expect(HttpOutboundSettingsSchema.safeParse(def).success).toBe(true);
  184. });
  185. it('wireguard parses once peer + secretKey are filled', () => {
  186. const def = createDefaultWireguardOutboundSettings({ secretKey: SAMPLE_SECRET });
  187. def.peers[0].publicKey = 'pk';
  188. def.peers[0].endpoint = `${SAMPLE_ADDRESS}:51820`;
  189. expect(WireguardOutboundSettingsSchema.safeParse(def).success).toBe(true);
  190. });
  191. it('hysteria parses once address is filled', () => {
  192. const def = createDefaultHysteriaOutboundSettings();
  193. def.address = SAMPLE_ADDRESS;
  194. expect(HysteriaOutboundSettingsSchema.safeParse(def).success).toBe(true);
  195. });
  196. });
  197. describe('createDefaultOutboundSettings dispatcher', () => {
  198. const PROTOCOLS = [
  199. 'freedom',
  200. 'blackhole',
  201. 'dns',
  202. 'vmess',
  203. 'vless',
  204. 'trojan',
  205. 'shadowsocks',
  206. 'socks',
  207. 'http',
  208. 'wireguard',
  209. 'hysteria',
  210. 'loopback',
  211. ];
  212. for (const protocol of PROTOCOLS) {
  213. it(`returns non-null for ${protocol}`, () => {
  214. expect(createDefaultOutboundSettings(protocol)).not.toBeNull();
  215. });
  216. }
  217. it('returns null for an unknown protocol', () => {
  218. expect(createDefaultOutboundSettings('mysterious')).toBeNull();
  219. });
  220. });