From 8ef703024b7972d6c369cc13c6ef3d9f08f10a26 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 28 Apr 2026 14:02:58 -0400 Subject: [PATCH] fix(cliproxy): probe routing via management endpoint --- src/cliproxy/management-api-client.ts | 5 +- src/cliproxy/routing-strategy-http.ts | 3 +- .../cliproxy/management-api-client.test.ts | 2 +- .../cliproxy/routing-strategy-http.test.ts | 65 +++++++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 tests/unit/cliproxy/routing-strategy-http.test.ts diff --git a/src/cliproxy/management-api-client.ts b/src/cliproxy/management-api-client.ts index dc9de153..f7d5c9d7 100644 --- a/src/cliproxy/management-api-client.ts +++ b/src/cliproxy/management-api-client.ts @@ -20,6 +20,7 @@ import type { CliproxyRoutingStrategy } from './types'; /** Default timeout for management operations (longer than health check) */ const DEFAULT_TIMEOUT_MS = 5000; +const ROUTING_STRATEGY_PATH = '/v0/management/routing/strategy'; /** Default port for HTTPS protocol */ const DEFAULT_HTTPS_PORT = 443; @@ -232,7 +233,7 @@ export class ManagementApiClient { * Get the global credential routing strategy from CLIProxy. */ async getRoutingStrategy(): Promise { - const response = await this.request<{ strategy?: string }>('GET', '/routing/strategy'); + const response = await this.request<{ strategy?: string }>('GET', ROUTING_STRATEGY_PATH); return response.data?.strategy === 'fill-first' ? 'fill-first' : 'round-robin'; } @@ -240,7 +241,7 @@ export class ManagementApiClient { * Update the global credential routing strategy on CLIProxy. */ async putRoutingStrategy(strategy: CliproxyRoutingStrategy): Promise { - await this.request('PUT', '/routing/strategy', { value: strategy }); + await this.request('PUT', ROUTING_STRATEGY_PATH, { value: strategy }); return strategy; } diff --git a/src/cliproxy/routing-strategy-http.ts b/src/cliproxy/routing-strategy-http.ts index 33d7b070..b0499fb0 100644 --- a/src/cliproxy/routing-strategy-http.ts +++ b/src/cliproxy/routing-strategy-http.ts @@ -7,13 +7,14 @@ import { } from './proxy-target-resolver'; const ROUTING_TIMEOUT_MS = 5000; +const CLIPROXY_ROUTING_MANAGEMENT_PATH = '/v0/management/routing/strategy'; export async function fetchCliproxyRoutingResponse( target: ProxyTarget, method: 'GET' | 'PUT', body?: Record ): Promise { - const url = buildProxyUrl(target, '/routing/strategy'); + const url = buildProxyUrl(target, CLIPROXY_ROUTING_MANAGEMENT_PATH); const headers = buildManagementHeaders( target, body ? { 'Content-Type': 'application/json' } : {} diff --git a/tests/unit/cliproxy/management-api-client.test.ts b/tests/unit/cliproxy/management-api-client.test.ts index 12641a25..df498534 100644 --- a/tests/unit/cliproxy/management-api-client.test.ts +++ b/tests/unit/cliproxy/management-api-client.test.ts @@ -128,7 +128,7 @@ describe('management-api-client', () => { expect(strategy).toBe('round-robin'); expect(fetchMock).toHaveBeenCalledWith( - 'http://localhost:8317/routing/strategy', + 'http://localhost:8317/v0/management/routing/strategy', expect.objectContaining({ method: 'PUT', body: JSON.stringify({ value: 'round-robin' }), diff --git a/tests/unit/cliproxy/routing-strategy-http.test.ts b/tests/unit/cliproxy/routing-strategy-http.test.ts new file mode 100644 index 00000000..d3887ada --- /dev/null +++ b/tests/unit/cliproxy/routing-strategy-http.test.ts @@ -0,0 +1,65 @@ +import { afterEach, describe, expect, it, mock } from 'bun:test'; +import { fetchCliproxyRoutingResponse } from '../../../src/cliproxy/routing-strategy-http'; +import type { ProxyTarget } from '../../../src/cliproxy/proxy-target-resolver'; + +describe('routing-strategy-http', () => { + const target: ProxyTarget = { + host: '127.0.0.1', + port: 8317, + protocol: 'http', + isRemote: false, + }; + + afterEach(() => { + mock.restore(); + }); + + it('reads the routing strategy from the management endpoint', async () => { + const originalFetch = global.fetch; + const fetchMock = mock(() => + Promise.resolve( + new Response(JSON.stringify({ strategy: 'round-robin' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + ) + ); + global.fetch = fetchMock as typeof global.fetch; + + await fetchCliproxyRoutingResponse(target, 'GET'); + + expect(fetchMock).toHaveBeenCalledWith( + 'http://127.0.0.1:8317/v0/management/routing/strategy', + expect.objectContaining({ + method: 'GET', + }) + ); + + global.fetch = originalFetch; + }); + + it('writes the routing strategy to the management endpoint', async () => { + const originalFetch = global.fetch; + const fetchMock = mock(() => + Promise.resolve( + new Response(JSON.stringify({ ok: true }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + ) + ); + global.fetch = fetchMock as typeof global.fetch; + + await fetchCliproxyRoutingResponse(target, 'PUT', { value: 'fill-first' }); + + expect(fetchMock).toHaveBeenCalledWith( + 'http://127.0.0.1:8317/v0/management/routing/strategy', + expect.objectContaining({ + method: 'PUT', + body: JSON.stringify({ value: 'fill-first' }), + }) + ); + + global.fetch = originalFetch; + }); +});