refactor/channel & ipc (#349)

Co-authored-by: paisley <8197966+su8su@users.noreply.github.com>
Co-authored-by: zuolingxuan <zuolingxuan@bytedance.com>
This commit is contained in:
Lingxuan Zuo
2026-03-09 19:04:00 +08:00
committed by GitHub
Unverified
parent 8b45960662
commit e28eba01e1
47 changed files with 4160 additions and 543 deletions

View File

@@ -8,11 +8,17 @@ import {
registerTransportInvoker,
unregisterTransportInvoker,
clearTransportBackoff,
getApiClientConfig,
applyGatewayTransportPreference,
createGatewayHttpTransportInvoker,
getGatewayWsDiagnosticEnabled,
setGatewayWsDiagnosticEnabled,
} from '@/lib/api-client';
describe('api-client', () => {
beforeEach(() => {
vi.resetAllMocks();
window.localStorage.removeItem('clawx:gateway-ws-diagnostic');
configureApiClient({
enabled: { ws: false, http: false },
rules: [{ matcher: /.*/, order: ['ipc'] }],
@@ -150,4 +156,82 @@ describe('api-client', () => {
expect(wsInvoker).toHaveBeenCalledTimes(2);
expect(invoke).toHaveBeenCalledTimes(2);
});
it('defaults transport preference to ipc-only', () => {
applyGatewayTransportPreference();
const config = getApiClientConfig();
expect(config.enabled.ws).toBe(false);
expect(config.enabled.http).toBe(false);
expect(config.rules[0]).toEqual({ matcher: /^gateway:rpc$/, order: ['ipc'] });
});
it('enables ws->http->ipc order when ws diagnostic is on', () => {
setGatewayWsDiagnosticEnabled(true);
expect(getGatewayWsDiagnosticEnabled()).toBe(true);
const config = getApiClientConfig();
expect(config.enabled.ws).toBe(true);
expect(config.enabled.http).toBe(true);
expect(config.rules[0]).toEqual({ matcher: /^gateway:rpc$/, order: ['ws', 'http', 'ipc'] });
});
it('parses gateway:httpProxy unified envelope response', async () => {
const invoke = vi.mocked(window.electron.ipcRenderer.invoke);
invoke.mockResolvedValueOnce({
ok: true,
data: {
status: 200,
ok: true,
json: { type: 'res', ok: true, payload: { rows: [1, 2] } },
},
});
const invoker = createGatewayHttpTransportInvoker();
const result = await invoker<{ success: boolean; result: { rows: number[] } }>(
'gateway:rpc',
['chat.history', { sessionKey: 's1' }],
);
expect(result.success).toBe(true);
expect(result.result.rows).toEqual([1, 2]);
expect(invoke).toHaveBeenCalledWith(
'gateway:httpProxy',
expect.objectContaining({
path: '/rpc',
method: 'POST',
}),
);
});
it('throws meaningful error when gateway:httpProxy unified envelope fails', async () => {
const invoke = vi.mocked(window.electron.ipcRenderer.invoke);
invoke.mockResolvedValueOnce({
ok: false,
error: { message: 'proxy unavailable' },
});
const invoker = createGatewayHttpTransportInvoker();
await expect(invoker('gateway:rpc', ['chat.history', {}])).rejects.toThrow('proxy unavailable');
});
it('normalizes raw gateway:httpProxy payload into ipc-style envelope', async () => {
const invoke = vi.mocked(window.electron.ipcRenderer.invoke);
invoke.mockResolvedValueOnce({
ok: true,
data: {
status: 200,
ok: true,
json: { channels: [{ id: 'telegram-default' }] },
},
});
const invoker = createGatewayHttpTransportInvoker();
const result = await invoker<{ success: boolean; result: { channels: Array<{ id: string }> } }>(
'gateway:rpc',
['channels.status', { probe: false }],
);
expect(result.success).toBe(true);
expect(result.result.channels[0].id).toBe('telegram-default');
});
});