mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-15 00:56:08 +00:00
c8ef1b1f68
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.
117 lines
3.3 KiB
TypeScript
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();
|
|
});
|
|
});
|