|
9 | 9 | "testing" |
10 | 10 | "time" |
11 | 11 |
|
| 12 | + "github.com/prometheus/client_golang/prometheus" |
| 13 | + promtest "github.com/prometheus/client_golang/prometheus/testutil" |
12 | 14 | "github.com/stretchr/testify/require" |
13 | 15 |
|
14 | 16 | "github.com/coder/aibridge" |
@@ -166,7 +168,7 @@ func TestIntegration(t *testing.T) { |
166 | 168 |
|
167 | 169 | logger := testutil.Logger(t) |
168 | 170 | providers := []aibridge.Provider{aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{BaseURL: mockOpenAI.URL})} |
169 | | - pool, err := aibridged.NewCachedBridgePool(aibridged.DefaultPoolOptions, providers, logger) |
| 171 | + pool, err := aibridged.NewCachedBridgePool(aibridged.DefaultPoolOptions, providers, nil, logger) |
170 | 172 | require.NoError(t, err) |
171 | 173 |
|
172 | 174 | // Given: aibridged is started. |
@@ -253,3 +255,109 @@ func TestIntegration(t *testing.T) { |
253 | 255 | // Then: the MCP server was initialized. |
254 | 256 | require.Contains(t, mcpTokenReceived, authLink.OAuthAccessToken, "mock MCP server not requested") |
255 | 257 | } |
| 258 | + |
| 259 | +// TestIntegrationWithMetrics validates that Prometheus metrics are correctly incremented |
| 260 | +// when requests are processed through aibridged. |
| 261 | +func TestIntegrationWithMetrics(t *testing.T) { |
| 262 | + t.Parallel() |
| 263 | + |
| 264 | + ctx := testutil.Context(t, testutil.WaitLong) |
| 265 | + |
| 266 | + // Create prometheus registry and metrics. |
| 267 | + registry := prometheus.NewRegistry() |
| 268 | + metrics := aibridge.NewMetrics(registry) |
| 269 | + |
| 270 | + // Set up mock OpenAI server. |
| 271 | + mockOpenAI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 272 | + w.Header().Set("Content-Type", "application/json") |
| 273 | + w.WriteHeader(http.StatusOK) |
| 274 | + _, _ = w.Write([]byte(`{ |
| 275 | + "id": "chatcmpl-test", |
| 276 | + "object": "chat.completion", |
| 277 | + "created": 1753343279, |
| 278 | + "model": "gpt-4.1", |
| 279 | + "choices": [ |
| 280 | + { |
| 281 | + "index": 0, |
| 282 | + "message": { |
| 283 | + "role": "assistant", |
| 284 | + "content": "test response" |
| 285 | + }, |
| 286 | + "finish_reason": "stop" |
| 287 | + } |
| 288 | + ], |
| 289 | + "usage": { |
| 290 | + "prompt_tokens": 10, |
| 291 | + "completion_tokens": 5, |
| 292 | + "total_tokens": 15 |
| 293 | + } |
| 294 | +}`)) |
| 295 | + })) |
| 296 | + t.Cleanup(mockOpenAI.Close) |
| 297 | + |
| 298 | + // Database and coderd setup. |
| 299 | + db, ps := dbtestutil.NewDB(t) |
| 300 | + client, _, api, firstUser := coderdenttest.NewWithAPI(t, &coderdenttest.Options{ |
| 301 | + Options: &coderdtest.Options{ |
| 302 | + Database: db, |
| 303 | + Pubsub: ps, |
| 304 | + }, |
| 305 | + }) |
| 306 | + |
| 307 | + userClient, _ := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID) |
| 308 | + |
| 309 | + // Create an API token for the user. |
| 310 | + apiKey, err := userClient.CreateToken(ctx, "me", codersdk.CreateTokenRequest{ |
| 311 | + TokenName: fmt.Sprintf("test-key-%d", time.Now().UnixNano()), |
| 312 | + Lifetime: time.Hour, |
| 313 | + Scope: codersdk.APIKeyScopeCoderAll, |
| 314 | + }) |
| 315 | + require.NoError(t, err) |
| 316 | + |
| 317 | + // Create aibridge client. |
| 318 | + aiBridgeClient, err := api.CreateInMemoryAIBridgeServer(ctx) |
| 319 | + require.NoError(t, err) |
| 320 | + |
| 321 | + logger := testutil.Logger(t) |
| 322 | + providers := []aibridge.Provider{aibridge.NewOpenAIProvider(aibridge.OpenAIConfig{BaseURL: mockOpenAI.URL})} |
| 323 | + |
| 324 | + // Create pool with metrics. |
| 325 | + pool, err := aibridged.NewCachedBridgePool(aibridged.DefaultPoolOptions, providers, metrics, logger) |
| 326 | + require.NoError(t, err) |
| 327 | + |
| 328 | + // Given: aibridged is started. |
| 329 | + srv, err := aibridged.New(ctx, pool, func(ctx context.Context) (aibridged.DRPCClient, error) { |
| 330 | + return aiBridgeClient, nil |
| 331 | + }, logger) |
| 332 | + require.NoError(t, err, "create new aibridged") |
| 333 | + t.Cleanup(func() { |
| 334 | + _ = srv.Shutdown(ctx) |
| 335 | + }) |
| 336 | + |
| 337 | + // When: a request is made to aibridged. |
| 338 | + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/openai/v1/chat/completions", bytes.NewBufferString(`{ |
| 339 | + "messages": [ |
| 340 | + { |
| 341 | + "role": "user", |
| 342 | + "content": "test message" |
| 343 | + } |
| 344 | + ], |
| 345 | + "model": "gpt-4.1" |
| 346 | +}`)) |
| 347 | + require.NoError(t, err, "make request to test server") |
| 348 | + req.Header.Add("Authorization", "Bearer "+apiKey.Key) |
| 349 | + req.Header.Add("Accept", "application/json") |
| 350 | + |
| 351 | + // When: aibridged handles the request. |
| 352 | + rec := httptest.NewRecorder() |
| 353 | + srv.ServeHTTP(rec, req) |
| 354 | + require.Equal(t, http.StatusOK, rec.Code) |
| 355 | + |
| 356 | + // Then: the interceptions metric should increase to 1. |
| 357 | + // This is not exhaustively checking the available metrics; just an indicative one to prove |
| 358 | + // the plumbing is working. |
| 359 | + require.Eventually(t, func() bool { |
| 360 | + count := promtest.ToFloat64(metrics.InterceptionCount) |
| 361 | + return count == 1 |
| 362 | + }, testutil.WaitShort, testutil.IntervalFast, "interceptions_total metric should be 1") |
| 363 | +} |
0 commit comments