mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 23:27:14 +00:00
fix(panel): read outbound protocol ids case-insensitively everywhere (#6523)
Six more readers compared an outbound's protocol id exactly while the core lowercases it, so an outbound spelled "Blackhole" passed every excludeBlackhole filter (offered as an mtproto egress, a dialerProxy target and the geodata download egress, all of which then drop the traffic) and one spelled "Freedom" was queued by Test All Outbounds. They now share isOutboundProtocol.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { isOutboundProtocol } from '@/schemas/primitives';
|
||||
|
||||
// xray-core lowercases a protocol id in LoadWithID before it resolves the
|
||||
// handler, so every panel reader has to accept the spellings it accepts.
|
||||
describe('isOutboundProtocol', () => {
|
||||
it.each([
|
||||
['canonical', { protocol: 'freedom' }],
|
||||
['capitalised', { protocol: 'Freedom' }],
|
||||
['upper', { protocol: 'FREEDOM' }],
|
||||
['mixed', { protocol: 'fReEdOm' }],
|
||||
])('matches a %s id', (_name, outbound) => {
|
||||
expect(isOutboundProtocol(outbound, 'freedom')).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['another protocol', { protocol: 'blackhole' }],
|
||||
['another spelling of another protocol', { protocol: 'Blackhole' }],
|
||||
['missing', {}],
|
||||
['empty', { protocol: '' }],
|
||||
['not a string', { protocol: 42 }],
|
||||
['null', null],
|
||||
['undefined', undefined],
|
||||
])('rejects %s', (_name, outbound) => {
|
||||
expect(isOutboundProtocol(outbound, 'freedom')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { useOutboundTagGroups, useOutboundTags } from '@/api/queries/useOutboundTags';
|
||||
import { makeTestQueryClient } from '@/test/test-utils';
|
||||
import { HttpUtil, Msg } from '@/utils';
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
// The core lowercases a protocol id before it resolves the handler, so a
|
||||
// template that spells the block outbound "Blackhole" still drops traffic.
|
||||
function mockConfig() {
|
||||
const payload = {
|
||||
xraySetting: {
|
||||
outbounds: [
|
||||
{ tag: 'direct', protocol: 'freedom' },
|
||||
{ tag: 'blocked', protocol: 'Blackhole' },
|
||||
{ tag: 'warp', protocol: 'wireguard' },
|
||||
],
|
||||
},
|
||||
};
|
||||
vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', JSON.stringify(payload)));
|
||||
}
|
||||
|
||||
function wrapperFor() {
|
||||
const queryClient = makeTestQueryClient();
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('outbound tag pickers', () => {
|
||||
it('excludes a block outbound whose id is spelled differently', async () => {
|
||||
mockConfig();
|
||||
|
||||
const { result } = renderHook(() => useOutboundTags({ excludeBlackhole: true }), {
|
||||
wrapper: wrapperFor(),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.data).toBeDefined());
|
||||
expect(result.current.data).toEqual(['direct', 'warp']);
|
||||
});
|
||||
|
||||
it('keeps the same tag in the grouped picker out of its outbound list', async () => {
|
||||
mockConfig();
|
||||
|
||||
const { result } = renderHook(() => useOutboundTagGroups({ excludeBlackhole: true }), {
|
||||
wrapper: wrapperFor(),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(result.current.data).toBeDefined());
|
||||
expect(result.current.data?.outbounds).toEqual(['direct', 'warp']);
|
||||
});
|
||||
|
||||
it('offers every tag when the caller does not exclude blocks', async () => {
|
||||
mockConfig();
|
||||
|
||||
const { result } = renderHook(() => useOutboundTags(), { wrapper: wrapperFor() });
|
||||
|
||||
await waitFor(() => expect(result.current.data).toBeDefined());
|
||||
expect(result.current.data).toEqual(['direct', 'blocked', 'warp']);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user