freedom-strategy-placement.test.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { describe, it, expect } from 'vitest';
  2. import { formValuesToWirePayload, rawOutboundToFormValues } from '@/lib/xray/outbound-form-adapter';
  3. // A freedom outbound resolves through sockopt.domainStrategy, and the core warns
  4. // on every load for both legacy placements it migrates there (infra/conf/xray.go).
  5. describe('freedom domain strategy placement', () => {
  6. it('emits the freedom card strategy into sockopt instead of settings', () => {
  7. const wire = formValuesToWirePayload(
  8. rawOutboundToFormValues({
  9. protocol: 'freedom',
  10. tag: 'direct',
  11. settings: { domainStrategy: 'UseIPv4' },
  12. }),
  13. );
  14. expect((wire.settings as Record<string, unknown>).domainStrategy).toBeUndefined();
  15. expect(wire.targetStrategy).toBeUndefined();
  16. expect(wire.streamSettings).toEqual({ sockopt: { domainStrategy: 'UseIPv4' } });
  17. });
  18. it('migrates a legacy settings key into sockopt on the next emit', () => {
  19. const wire = formValuesToWirePayload(
  20. rawOutboundToFormValues({
  21. protocol: 'freedom',
  22. tag: 'direct',
  23. settings: { domainStrategy: 'UseIPv6' },
  24. }),
  25. );
  26. expect(wire.streamSettings).toEqual({ sockopt: { domainStrategy: 'UseIPv6' } });
  27. expect((wire.settings as Record<string, unknown>).domainStrategy).toBeUndefined();
  28. });
  29. it('migrates a legacy outbound-root targetStrategy into sockopt', () => {
  30. const wire = formValuesToWirePayload(
  31. rawOutboundToFormValues({
  32. protocol: 'freedom',
  33. tag: 'direct',
  34. targetStrategy: 'ForceIPv4',
  35. settings: {},
  36. }),
  37. );
  38. expect(wire.targetStrategy).toBeUndefined();
  39. expect(wire.streamSettings).toEqual({ sockopt: { domainStrategy: 'ForceIPv4' } });
  40. });
  41. it('reads the sockopt strategy back into the freedom card', () => {
  42. const values = rawOutboundToFormValues({
  43. protocol: 'freedom',
  44. tag: 'direct',
  45. streamSettings: { sockopt: { domainStrategy: 'UseIPv4v6' } },
  46. settings: {},
  47. });
  48. expect((values.settings as { domainStrategy?: string }).domainStrategy).toBe('UseIPv4v6');
  49. });
  50. it('keeps the freedom card empty rather than showing a stale legacy value twice', () => {
  51. const values = rawOutboundToFormValues({
  52. protocol: 'freedom',
  53. tag: 'direct',
  54. targetStrategy: 'UseIPv4',
  55. settings: {},
  56. });
  57. expect(values.targetStrategy).toBe('');
  58. expect((values.settings as { domainStrategy?: string }).domainStrategy).toBe('UseIPv4');
  59. });
  60. it('shows the strategy the core will run with when a legacy key outranks sockopt', () => {
  61. const values = rawOutboundToFormValues({
  62. protocol: 'freedom',
  63. tag: 'direct',
  64. targetStrategy: 'UseIPv4',
  65. streamSettings: { sockopt: { domainStrategy: 'UseIPv6' } },
  66. settings: {},
  67. });
  68. expect((values.settings as { domainStrategy?: string }).domainStrategy).toBe('UseIPv4');
  69. const wire = formValuesToWirePayload(values);
  70. expect(wire.targetStrategy).toBeUndefined();
  71. expect(wire.streamSettings).toEqual({ sockopt: { domainStrategy: 'UseIPv4' } });
  72. });
  73. it('drops the sockopt key when the card is cleared', () => {
  74. const values = rawOutboundToFormValues({
  75. protocol: 'freedom',
  76. tag: 'direct',
  77. streamSettings: { sockopt: { domainStrategy: 'UseIPv4', tcpFastOpen: true } },
  78. settings: {},
  79. });
  80. (values.settings as { domainStrategy?: string }).domainStrategy = '';
  81. const wire = formValuesToWirePayload(values);
  82. expect(wire.streamSettings).toEqual({ sockopt: { tcpFastOpen: true } });
  83. });
  84. it('normalizes the sockopt spelling the core matches case-insensitively', () => {
  85. const wire = formValuesToWirePayload(
  86. rawOutboundToFormValues({
  87. protocol: 'freedom',
  88. tag: 'direct',
  89. streamSettings: { sockopt: { domainStrategy: 'useipv4v6' } },
  90. settings: {},
  91. }),
  92. );
  93. expect(wire.streamSettings).toEqual({ sockopt: { domainStrategy: 'UseIPv4v6' } });
  94. });
  95. it('leaves AsIs out of the wire entirely', () => {
  96. const wire = formValuesToWirePayload(
  97. rawOutboundToFormValues({
  98. protocol: 'freedom',
  99. tag: 'direct',
  100. settings: { domainStrategy: 'AsIs' },
  101. }),
  102. );
  103. expect(wire.streamSettings).toBeUndefined();
  104. expect((wire.settings as Record<string, unknown>).domainStrategy).toBeUndefined();
  105. });
  106. it('merges into a sockopt the transport form already carries', () => {
  107. const wire = formValuesToWirePayload(
  108. rawOutboundToFormValues({
  109. protocol: 'freedom',
  110. tag: 'direct',
  111. settings: { domainStrategy: 'UseIPv4' },
  112. streamSettings: { sockopt: { tcpFastOpen: true } },
  113. }),
  114. );
  115. expect(wire.streamSettings).toEqual({
  116. sockopt: { tcpFastOpen: true, domainStrategy: 'UseIPv4' },
  117. });
  118. });
  119. it('still emits the root targetStrategy for protocols that use it there', () => {
  120. const wire = formValuesToWirePayload(
  121. rawOutboundToFormValues({
  122. protocol: 'vless',
  123. tag: 'proxy',
  124. targetStrategy: 'UseIPv4',
  125. settings: { address: 'example.com', port: 443, id: 'x', encryption: 'none' },
  126. }),
  127. );
  128. expect(wire.targetStrategy).toBe('UseIPv4');
  129. });
  130. });