feat(frontend): protocol capability predicates as pure functions

Adds lib/xray/protocol-capabilities.ts with the seven predicates the
modals call: canEnableTls, canEnableReality, canEnableTlsFlow,
canEnableStream, canEnableVisionSeed, isSS2022, isSSMultiUser. Each
takes a minimal slice of an InboundFormValues, no class instance.

The legacy isSSMultiUser returns true on non-shadowsocks protocols too
(method getter resolves to "" which != blake3-chacha20-poly1305). The
new function preserves this quirk and documents it inline; callers all
narrow on protocol === shadowsocks before checking, so the surprising
return value never surfaces.

Parity harness in test/protocol-capabilities.test.ts crosses each of
the 10 golden fixtures with 14 stream configurations (network × security)
and asserts each predicate matches the legacy class method — 140 cases,
all green.
This commit is contained in:
MHSanaei
2026-05-26 01:53:16 +02:00
parent 629567db72
commit 142ed97cc0
2 changed files with 159 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
/// <reference types="vite/client" />
import { describe, expect, it } from 'vitest';
import { Inbound } from '@/models/inbound';
import {
canEnableTls,
canEnableReality,
canEnableTlsFlow,
canEnableStream,
canEnableVisionSeed,
isSS2022,
isSSMultiUser,
} from '@/lib/xray/protocol-capabilities';
// Parity harness for the capability predicates. For each golden fixture
// (protocol+settings), cross with a matrix of stream configurations
// (network × security), build the legacy Inbound class via fromJson, and
// assert each pure-function predicate matches the class method.
//
// Only the (protocol × stream-shape) cross matters here — the predicates
// never read sniffing/port/listen, so we hold those constant.
const fixtures = import.meta.glob<unknown>(
'./golden/fixtures/inbound/*.json',
{ eager: true, import: 'default' },
);
interface FixtureShape { protocol: string; settings: Record<string, unknown> }
const STREAM_CASES: { network: string; security: string }[] = [
{ network: 'tcp', security: 'none' },
{ network: 'tcp', security: 'tls' },
{ network: 'tcp', security: 'reality' },
{ network: 'ws', security: 'none' },
{ network: 'ws', security: 'tls' },
{ network: 'grpc', security: 'none' },
{ network: 'grpc', security: 'tls' },
{ network: 'grpc', security: 'reality' },
{ network: 'kcp', security: 'none' },
{ network: 'httpupgrade', security: 'none' },
{ network: 'httpupgrade', security: 'tls' },
{ network: 'xhttp', security: 'none' },
{ network: 'xhttp', security: 'tls' },
{ network: 'xhttp', security: 'reality' },
];
function fixtureName(path: string): string {
return (path.split('/').pop() ?? path).replace(/\.json$/, '');
}
describe('protocol capability predicates: pure ↔ legacy parity', () => {
const entries = Object.entries(fixtures).sort(([a], [b]) => a.localeCompare(b));
for (const [path, raw] of entries) {
const name = fixtureName(path);
const fix = raw as FixtureShape;
for (const stream of STREAM_CASES) {
it(`${name} :: ${stream.network}/${stream.security}`, () => {
const wireConfig = {
port: 12345,
listen: '127.0.0.1',
protocol: fix.protocol,
settings: fix.settings,
streamSettings: { network: stream.network, security: stream.security },
sniffing: {},
};
const legacy = Inbound.fromJson(wireConfig);
const values = {
protocol: fix.protocol,
streamSettings: { network: stream.network, security: stream.security },
settings: fix.settings,
};
expect(canEnableTls(values)).toBe(legacy.canEnableTls());
expect(canEnableReality(values)).toBe(legacy.canEnableReality());
expect(canEnableTlsFlow(values)).toBe(legacy.canEnableTlsFlow());
expect(canEnableStream(values)).toBe(legacy.canEnableStream());
expect(canEnableVisionSeed(values)).toBe(legacy.canEnableVisionSeed());
expect(isSS2022(values)).toBe(legacy.isSS2022);
expect(isSSMultiUser(values)).toBe(legacy.isSSMultiUser);
});
}
}
});