|
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 | +funcTestIntegrationWithMetrics(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 | +returnaiBridgeClient,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 | +returncount==1 |
| 362 | +},testutil.WaitShort,testutil.IntervalFast,"interceptions_total metric should be 1") |
| 363 | +} |