|
|
@@ -8,6 +8,7 @@ import {
|
|
|
normalizeStreamSettingsForWire,
|
|
|
normalizeXhttpForWire,
|
|
|
validateRealityTarget,
|
|
|
+ xmuxSiblingOnEdit,
|
|
|
} from '@/lib/xray/stream-wire-normalize';
|
|
|
import { InboundFormSchema } from '@/schemas/forms/inbound-form';
|
|
|
import { HappyEyeballsSchema } from '@/schemas/protocols/stream/sockopt';
|
|
|
@@ -167,6 +168,68 @@ describe('normalizeXhttpForWire stream-one', () => {
|
|
|
expect(xmux.maxConnections).toBe(6);
|
|
|
expect(xmux).not.toHaveProperty('maxConcurrency');
|
|
|
});
|
|
|
+
|
|
|
+ // #5864 repro: editing maxConcurrency alone used to always lose to the
|
|
|
+ // maxConnections=6 default because both defaulted non-zero at once. The
|
|
|
+ // form now live-clears the untouched sibling to 0 (see xmuxSiblingOnEdit
|
|
|
+ // below) before save, so the exclusivity check above has nothing to
|
|
|
+ // collide with and the explicit maxConcurrency edit survives.
|
|
|
+ it('keeps an explicit maxConcurrency edit once the sibling has been live-cleared to 0', () => {
|
|
|
+ const out = normalizeXhttpForWire({
|
|
|
+ path: '/app',
|
|
|
+ mode: 'auto',
|
|
|
+ enableXmux: true,
|
|
|
+ xmux: { ...XHttpXmuxSchema.parse({}), maxConcurrency: '1-2', maxConnections: 0 },
|
|
|
+ }, 'inbound');
|
|
|
+
|
|
|
+ const xmux = out.xmux as Record<string, unknown>;
|
|
|
+ expect(xmux.maxConcurrency).toBe('1-2');
|
|
|
+ expect(xmux.maxConnections).toBe(0);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('drops maxConcurrency when it is left as an empty string, regardless of maxConnections', () => {
|
|
|
+ const out = normalizeXhttpForWire({
|
|
|
+ path: '/app',
|
|
|
+ mode: 'auto',
|
|
|
+ enableXmux: true,
|
|
|
+ xmux: { ...XHttpXmuxSchema.parse({}), maxConcurrency: '', maxConnections: '10' },
|
|
|
+ }, 'outbound');
|
|
|
+
|
|
|
+ const xmux = out.xmux as Record<string, unknown>;
|
|
|
+ expect(xmux).not.toHaveProperty('maxConcurrency');
|
|
|
+ expect(xmux.maxConnections).toBe('10');
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+describe('xmuxSiblingOnEdit', () => {
|
|
|
+ it('is a no-op while the edited field is still empty/zero', () => {
|
|
|
+ expect(xmuxSiblingOnEdit('maxConcurrency', '')).toBeUndefined();
|
|
|
+ expect(xmuxSiblingOnEdit('maxConcurrency', '0-0')).toBeUndefined();
|
|
|
+ expect(xmuxSiblingOnEdit('maxConnections', 0)).toBeUndefined();
|
|
|
+ expect(xmuxSiblingOnEdit('maxConnections', '')).toBeUndefined();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('clears maxConnections to 0 the instant maxConcurrency becomes a real value', () => {
|
|
|
+ expect(xmuxSiblingOnEdit('maxConcurrency', '1-2')).toEqual({
|
|
|
+ field: 'maxConnections',
|
|
|
+ value: 0,
|
|
|
+ });
|
|
|
+ expect(xmuxSiblingOnEdit('maxConcurrency', '5')).toEqual({
|
|
|
+ field: 'maxConnections',
|
|
|
+ value: 0,
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('clears maxConcurrency to "" the instant maxConnections becomes a real value', () => {
|
|
|
+ expect(xmuxSiblingOnEdit('maxConnections', '10')).toEqual({
|
|
|
+ field: 'maxConcurrency',
|
|
|
+ value: '',
|
|
|
+ });
|
|
|
+ expect(xmuxSiblingOnEdit('maxConnections', 4)).toEqual({
|
|
|
+ field: 'maxConcurrency',
|
|
|
+ value: '',
|
|
|
+ });
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
describe('normalizeSockoptForWire', () => {
|