fix(feishu): feishu connector name validate (#797)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,9 @@ import { Channels } from '@/pages/Channels/index';
|
||||
|
||||
const hostApiFetchMock = vi.fn();
|
||||
const subscribeHostEventMock = vi.fn();
|
||||
const toastSuccessMock = vi.fn();
|
||||
const toastErrorMock = vi.fn();
|
||||
const toastWarningMock = vi.fn();
|
||||
|
||||
const { gatewayState } = vi.hoisted(() => ({
|
||||
gatewayState: {
|
||||
@@ -31,9 +34,9 @@ vi.mock('react-i18next', () => ({
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
success: (...args: unknown[]) => toastSuccessMock(...args),
|
||||
error: (...args: unknown[]) => toastErrorMock(...args),
|
||||
warning: (...args: unknown[]) => toastWarningMock(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -83,6 +86,94 @@ describe('Channels page status refresh', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('blocks saving when custom account ID is non-canonical', async () => {
|
||||
subscribeHostEventMock.mockImplementation(() => vi.fn());
|
||||
hostApiFetchMock.mockImplementation(async (path: string) => {
|
||||
if (path === '/api/channels/accounts') {
|
||||
return {
|
||||
success: true,
|
||||
channels: [
|
||||
{
|
||||
channelType: 'feishu',
|
||||
defaultAccountId: 'default',
|
||||
status: 'connected',
|
||||
accounts: [
|
||||
{
|
||||
accountId: 'default',
|
||||
name: 'Primary Account',
|
||||
configured: true,
|
||||
status: 'connected',
|
||||
isDefault: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (path === '/api/agents') {
|
||||
return {
|
||||
success: true,
|
||||
agents: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (path === '/api/channels/credentials/validate') {
|
||||
return {
|
||||
success: true,
|
||||
valid: true,
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (path === '/api/channels/config') {
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected host API path: ${path}`);
|
||||
});
|
||||
|
||||
render(<Channels />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Feishu / Lark')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'account.add' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('dialog.configureTitle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByLabelText('account.customIdLabel'), {
|
||||
target: { value: '测试账号' },
|
||||
});
|
||||
const appIdInput = document.getElementById('appId') as HTMLInputElement | null;
|
||||
const appSecretInput = document.getElementById('appSecret') as HTMLInputElement | null;
|
||||
expect(appIdInput).not.toBeNull();
|
||||
expect(appSecretInput).not.toBeNull();
|
||||
fireEvent.change(appIdInput!, { target: { value: 'cli_test' } });
|
||||
fireEvent.change(appSecretInput!, { target: { value: 'secret_test' } });
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'dialog.saveAndConnect' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('account.invalidCanonicalId')).toBeInTheDocument();
|
||||
});
|
||||
expect(toastErrorMock).toHaveBeenCalledWith('account.invalidCanonicalId');
|
||||
|
||||
const saveCalls = hostApiFetchMock.mock.calls.filter(([path, init]) => (
|
||||
path === '/api/channels/config'
|
||||
&& typeof init === 'object'
|
||||
&& init != null
|
||||
&& 'method' in init
|
||||
&& (init as { method?: string }).method === 'POST'
|
||||
));
|
||||
expect(saveCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('refetches channel accounts when gateway channel-status events arrive', async () => {
|
||||
let channelStatusHandler: (() => void) | undefined;
|
||||
subscribeHostEventMock.mockImplementation((eventName: string, handler: () => void) => {
|
||||
|
||||
Reference in New Issue
Block a user