|
|
@@ -1,9 +1,10 @@
|
|
|
-import { describe, it, expect } from 'vitest';
|
|
|
-import { screen, act, render, cleanup } from '@testing-library/react';
|
|
|
+import { describe, it, expect, vi } from 'vitest';
|
|
|
+import { screen, act, render, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
|
|
|
|
|
import InboundFormModal from '@/pages/inbounds/form/InboundFormModal';
|
|
|
import { DBInbound } from '@/models/dbinbound';
|
|
|
import { ThemeProvider } from '@/hooks/useTheme';
|
|
|
+import { HttpUtil } from '@/utils';
|
|
|
import {
|
|
|
renderWithProviders,
|
|
|
fieldLabels,
|
|
|
@@ -11,6 +12,19 @@ import {
|
|
|
chooseSelectOption,
|
|
|
} from './test-utils';
|
|
|
|
|
|
+const { messageError } = vi.hoisted(() => ({ messageError: vi.fn() }));
|
|
|
+
|
|
|
+vi.mock('antd', async (importOriginal) => {
|
|
|
+ const actual = await importOriginal<typeof import('antd')>();
|
|
|
+ return {
|
|
|
+ ...actual,
|
|
|
+ message: {
|
|
|
+ ...actual.message,
|
|
|
+ useMessage: () => [{ error: messageError }, null],
|
|
|
+ },
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
function renderModal() {
|
|
|
return renderWithProviders(
|
|
|
<InboundFormModal
|
|
|
@@ -25,6 +39,63 @@ function renderModal() {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+function primaryButton(): HTMLElement {
|
|
|
+ const button = document.querySelector('.ant-modal-footer .ant-btn-primary');
|
|
|
+ if (!button) throw new Error('Primary modal button not found');
|
|
|
+ return button as HTMLElement;
|
|
|
+}
|
|
|
+
|
|
|
+function cloneLikeVlessInbound(target: string) {
|
|
|
+ return new DBInbound({
|
|
|
+ id: 42,
|
|
|
+ port: 41234,
|
|
|
+ listen: '',
|
|
|
+ protocol: 'vless',
|
|
|
+ remark: 'source clone',
|
|
|
+ enable: false,
|
|
|
+ settings: {
|
|
|
+ clients: [],
|
|
|
+ decryption: 'none',
|
|
|
+ encryption: 'none',
|
|
|
+ fallbacks: [],
|
|
|
+ },
|
|
|
+ streamSettings: {
|
|
|
+ network: 'tcp',
|
|
|
+ security: 'reality',
|
|
|
+ tcpSettings: { header: { type: 'none' } },
|
|
|
+ realitySettings: {
|
|
|
+ target,
|
|
|
+ serverNames: ['example.com'],
|
|
|
+ privateKey: 'test-private-key',
|
|
|
+ shortIds: ['abcd'],
|
|
|
+ settings: {
|
|
|
+ publicKey: 'test-public-key',
|
|
|
+ fingerprint: 'chrome',
|
|
|
+ spiderX: '/',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ sniffing: { enabled: false },
|
|
|
+ nodeId: null,
|
|
|
+ shareAddrStrategy: 'listen',
|
|
|
+ shareAddr: '',
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function renderCloneLikeEdit(dbInbound: DBInbound) {
|
|
|
+ renderWithProviders(
|
|
|
+ <InboundFormModal
|
|
|
+ open
|
|
|
+ mode="edit"
|
|
|
+ dbInbound={dbInbound}
|
|
|
+ dbInbounds={[dbInbound]}
|
|
|
+ availableNodes={[]}
|
|
|
+ onClose={() => {}}
|
|
|
+ onSaved={() => {}}
|
|
|
+ />,
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
describe('InboundFormModal', () => {
|
|
|
it('renders add mode without crashing', () => {
|
|
|
renderModal();
|
|
|
@@ -141,4 +212,37 @@ describe('InboundFormModal', () => {
|
|
|
expect(strategyItem('Node address')).toBeTruthy();
|
|
|
expect(strategyItem('Inbound listen')).toBeFalsy();
|
|
|
});
|
|
|
+
|
|
|
+ it('surfaces a Reality validation error and switches to its tab', async () => {
|
|
|
+ const post = vi.mocked(HttpUtil.post);
|
|
|
+ post.mockClear();
|
|
|
+ messageError.mockClear();
|
|
|
+ renderCloneLikeEdit(cloneLikeVlessInbound('example.com'));
|
|
|
+
|
|
|
+ fireEvent.click(primaryButton());
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const securityTab = screen.getByRole('tab', { name: 'Security' });
|
|
|
+ expect(securityTab.getAttribute('aria-selected')).toBe('true');
|
|
|
+ });
|
|
|
+ expect(messageError).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining('REALITY target must include a port'),
|
|
|
+ );
|
|
|
+ expect(post).not.toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('submits a valid clone-like Reality inbound', async () => {
|
|
|
+ const post = vi.mocked(HttpUtil.post);
|
|
|
+ post.mockClear();
|
|
|
+ renderCloneLikeEdit(cloneLikeVlessInbound('example.com:443'));
|
|
|
+
|
|
|
+ fireEvent.click(primaryButton());
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(post).toHaveBeenCalledWith(
|
|
|
+ '/panel/api/inbounds/update/42',
|
|
|
+ expect.objectContaining({ enable: false, port: 41234, protocol: 'vless' }),
|
|
|
+ );
|
|
|
+ });
|
|
|
+ });
|
|
|
});
|