Files
3x-ui/frontend/src/test/inbound-form-blocks.test.tsx
T
MHSanaei c8ef1b1f68 feat(reality): derive a stable per-client spiderX for shared links
The inbound's spiderX now acts as a per-client seed: exports emit
sha256(seed|subKey) truncated to a 15-hex "/path", so a client's spx no
longer changes on every subscription fetch (#5718) while different
clients stop sharing one fingerprintable value. The form gains a
regenerate button that rotates every client's path at once.

The frontend link builders derive through the same function
(lib/xray/spider-x.ts, @noble/hashes) keyed on subId-then-email like
the Go subKey, so panel QR/copy links and subscription output agree —
cross-language vector tests lock both sides byte-for-byte. streamData
now tolerates malformed stored stream settings (unparseable JSON, null
tls/reality settings) instead of panicking the subscription request.
2026-07-02 12:53:08 +02:00

117 lines
3.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Form, type FormInstance } from 'antd';
import type { ReactNode } from 'react';
import {
GrpcForm,
HttpUpgradeForm,
KcpForm,
RawForm,
SockoptForm,
WsForm,
XhttpForm,
} from '@/pages/inbounds/form/transport';
import { RealityForm, TlsForm } from '@/pages/inbounds/form/security';
import type { InboundFormValues } from '@/schemas/forms/inbound-form';
import { renderWithProviders, fieldLabels } from './test-utils';
function FormHarness({
children,
initialValues,
}: {
children: (form: FormInstance<InboundFormValues>) => ReactNode;
initialValues?: Record<string, unknown>;
}) {
const [form] = Form.useForm<InboundFormValues>();
return <Form form={form} initialValues={initialValues}>{children(form)}</Form>;
}
function renderInForm(
node: (form: FormInstance<InboundFormValues>) => ReactNode,
initialValues?: Record<string, unknown>,
) {
return renderWithProviders(<FormHarness initialValues={initialValues}>{node}</FormHarness>);
}
const noop = () => {};
describe('inbound transport forms', () => {
it('RawForm field structure is stable', () => {
renderInForm(() => <RawForm />);
expect(fieldLabels()).toMatchSnapshot();
});
it('WsForm field structure is stable', () => {
renderInForm(() => <WsForm />);
expect(fieldLabels()).toMatchSnapshot();
});
it('GrpcForm field structure is stable', () => {
renderInForm(() => <GrpcForm />);
expect(fieldLabels()).toMatchSnapshot();
});
it('KcpForm field structure is stable', () => {
renderInForm(() => <KcpForm />);
expect(fieldLabels()).toMatchSnapshot();
});
it('HttpUpgradeForm field structure is stable', () => {
renderInForm(() => <HttpUpgradeForm />);
expect(fieldLabels()).toMatchSnapshot();
});
it('XhttpForm field structure is stable', () => {
renderInForm((form) => <XhttpForm form={form} />);
expect(fieldLabels()).toMatchSnapshot();
});
it('SockoptForm field structure is stable (server-side fields only)', () => {
// The inbound sockopt form shows only server/listening-side fields;
// outbound-only fields (dialerProxy, domainStrategy, interface,
// addressPortStrategy, happyEyeballs, tcpMptcp) live in the outbound form.
renderInForm(
() => <SockoptForm toggleSockopt={noop} network="tcp" />,
{ streamSettings: { sockopt: { mark: 0 } } },
);
expect(fieldLabels()).toMatchSnapshot();
});
});
describe('inbound security forms', () => {
it('TlsForm field structure is stable', () => {
renderInForm(() => (
<TlsForm
saving={false}
setCertFromPanel={noop}
clearCertFiles={noop}
pinFromCert={noop}
pinFromRemote={noop}
getNewEchCert={noop}
clearEchCert={noop}
/>
));
expect(fieldLabels()).toMatchSnapshot();
});
it('RealityForm field structure is stable', () => {
renderInForm(() => (
<RealityForm
saving={false}
scanning={false}
scanResult={null}
scanRealityTarget={noop}
scanRealityCandidates={async () => []}
applyRealityScanResult={noop}
randomizeShortIds={noop}
randomizeSpiderX={noop}
genRealityKeypair={noop}
clearRealityKeypair={noop}
genMldsa65={noop}
clearMldsa65={noop}
/>
));
expect(fieldLabels()).toMatchSnapshot();
});
});