Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
dff66d9
refactor: create internal/integrationtest package infrastructure
pawbana Mar 5, 2026
934b816
refactor: migrate metrics and trace integration tests to internal/int…
pawbana Mar 5, 2026
6bd29e0
refactor: migrate responses, circuit_breaker, and apidump integration…
pawbana Mar 5, 2026
cd91c6c
Finish bridge_test.go migration: replace all configureFunc/inline boi…
pawbana Mar 5, 2026
d6e6df0
refactor: convert bridge_test.go from external to internal test package
pawbana Mar 5, 2026
0c8152e
refactor: use internal test package, unexport all helpers
pawbana Mar 5, 2026
23eb779
refactor: split bridge_test.go into per-interceptor test files
pawbana Mar 5, 2026
11f6f37
fixup: apply unexport renames to remaining helper files
pawbana Mar 5, 2026
82ea368
remove providerFunc
pawbana Mar 5, 2026
e3d0f14
merge tests back into bridge_test.go
pawbana Mar 6, 2026
741133c
bridge.go -> setupbridge.go + helpers.go
pawbana Mar 6, 2026
c0541df
remove withWrappedRecorder
pawbana Mar 6, 2026
e7aaf92
remove actorID arg from setupInjectedToolTest
pawbana Mar 6, 2026
94e7ccc
use bridgeTestServer.newRequest
pawbana Mar 6, 2026
9c0a25a
refactor: update makeRequest call sites in trace_test.go for new *htt…
pawbana Mar 6, 2026
e93d096
refactor: update makeRequest call sites in responses_test.go
pawbana Mar 6, 2026
52b17c6
refactor: update makeRequest call sites in bridge_test.go
pawbana Mar 6, 2026
9755905
bridgeServer.makeRequest + tracerSetup + TestSessionIDTracking cleanup
pawbana Mar 6, 2026
b48a153
cleanup
pawbana Mar 6, 2026
8860414
reorder tests in bridge_test.go to original order
pawbana Mar 6, 2026
89a70b7
remove not needed withLogger
pawbana Mar 6, 2026
cfa535f
fix comments
pawbana Mar 6, 2026
5bf7bc7
rename
pawbana Mar 6, 2026
dfa21f0
formatting fix
pawbana Mar 6, 2026
a482ec1
reorder to previous
pawbana Mar 6, 2026
4291a3a
recorder cleanup
pawbana Mar 6, 2026
b0e32be
MockRecorder Total*Tokens
pawbana Mar 6, 2026
8f49730
add Body.Close() to bridgeServer.makeRequest + some test names cleanup
pawbana Mar 6, 2026
cf4208e
changed TestResponsesParallelToolsOverwritten and TestOpenAIChatCompl…
pawbana Mar 6, 2026
1086b3b
fmt fix
pawbana Mar 9, 2026
41e9598
review 1: fixed setting headers in makeRequest, removed unneded provi…
pawbana Mar 9, 2026
0f25518
review 2
pawbana Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package aibridge_test
package integrationtest

import (
"bufio"
"bytes"
"context"
"io"
"net"
"net/http"
"net/http/httptest"
"os"
Expand All @@ -14,105 +13,74 @@ import (
"testing"
"time"

"cdr.dev/slog/v3"
"cdr.dev/slog/v3/sloggers/slogtest"
"github.com/coder/aibridge"
"github.com/coder/aibridge/config"
aibcontext "github.com/coder/aibridge/context"
"github.com/coder/aibridge/fixtures"
"github.com/coder/aibridge/intercept/apidump"
"github.com/coder/aibridge/internal/testutil"
"github.com/coder/aibridge/provider"
"github.com/stretchr/testify/require"
)

func openaiCfgWithAPIDump(url, key, dumpDir string) config.OpenAI {
return config.OpenAI{
BaseURL: url,
Key: key,
APIDumpDir: dumpDir,
}
}

func anthropicCfgWithAPIDump(url, key, dumpDir string) config.Anthropic {
return config.Anthropic{
BaseURL: url,
Key: key,
APIDumpDir: dumpDir,
}
}

func TestAPIDump(t *testing.T) {
t.Parallel()

cases := []struct {
name string
fixture []byte
providersFunc func(addr, dumpDir string) []aibridge.Provider
createRequestFunc createRequestFunc
name string
fixture []byte
providerFunc func(addr, dumpDir string) aibridge.Provider
path string
}{
{
name: "anthropic",
fixture: fixtures.AntSimple,
providersFunc: func(addr, dumpDir string) []aibridge.Provider {
return []aibridge.Provider{provider.NewAnthropic(anthropicCfgWithAPIDump(addr, apiKey, dumpDir), nil)}
providerFunc: func(addr, dumpDir string) aibridge.Provider {
return provider.NewAnthropic(anthropicCfgWithAPIDump(addr, apiKey, dumpDir), nil)
},
createRequestFunc: createAnthropicMessagesReq,
path: pathAnthropicMessages,
},
{
name: "openai_chat_completions",
fixture: fixtures.OaiChatSimple,
providersFunc: func(addr, dumpDir string) []aibridge.Provider {
return []aibridge.Provider{provider.NewOpenAI(openaiCfgWithAPIDump(addr, apiKey, dumpDir))}
providerFunc: func(addr, dumpDir string) aibridge.Provider {
return provider.NewOpenAI(openaiCfgWithAPIDump(addr, apiKey, dumpDir))
},
createRequestFunc: createOpenAIChatCompletionsReq,
path: pathOpenAIChatCompletions,
},
{
name: "openai_responses",
fixture: fixtures.OaiResponsesBlockingSimple,
providersFunc: func(addr, dumpDir string) []aibridge.Provider {
return []aibridge.Provider{provider.NewOpenAI(openaiCfgWithAPIDump(addr, apiKey, dumpDir))}
providerFunc: func(addr, dumpDir string) aibridge.Provider {
return provider.NewOpenAI(openaiCfgWithAPIDump(addr, apiKey, dumpDir))
},
createRequestFunc: createOpenAIResponsesReq,
path: pathOpenAIResponses,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug)

ctx, cancel := context.WithTimeout(t.Context(), time.Second*30)
t.Cleanup(cancel)

// Setup mock upstream server.
fix := fixtures.Parse(t, tc.fixture)
srv := testutil.NewMockUpstream(t, ctx, testutil.NewFixtureResponse(fix))
srv := newMockUpstream(t, ctx, newFixtureResponse(fix))

// Create temp dir for API dumps.
dumpDir := t.TempDir()

recorderClient := &testutil.MockRecorder{}
b, err := aibridge.NewRequestBridge(t.Context(), tc.providersFunc(srv.URL, dumpDir), recorderClient, testutil.NewNoopMCPManager(), logger, nil, testTracer)
require.NoError(t, err)

mockSrv := httptest.NewUnstartedServer(b)
t.Cleanup(mockSrv.Close)
mockSrv.Config.BaseContext = func(_ net.Listener) context.Context {
return aibcontext.AsActor(ctx, userID, nil)
}
mockSrv.Start()
bridgeServer := newBridgeTestServer(t, ctx, srv.URL,
withCustomProvider(tc.providerFunc(srv.URL, dumpDir)),
)

req := tc.createRequestFunc(t, mockSrv.URL, fix.Request())
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
resp := bridgeServer.makeRequest(t, http.MethodPost, tc.path, fix.Request())
require.Equal(t, http.StatusOK, resp.StatusCode)
defer resp.Body.Close()
_, _ = io.ReadAll(resp.Body)
_, err := io.ReadAll(resp.Body)
require.NoError(t, err)

// Verify dump files were created.
interceptions := recorderClient.RecordedInterceptions()
interceptions := bridgeServer.Recorder.RecordedInterceptions()
require.Len(t, interceptions, 1)
interceptionID := interceptions[0].ID

Expand Down Expand Up @@ -167,7 +135,7 @@ func TestAPIDump(t *testing.T) {
expectedRespBody := fix.NonStreaming()
require.JSONEq(t, string(expectedRespBody), string(dumpRespBody), "response body JSON should match semantically")

recorderClient.VerifyAllInterceptionsEnded(t)
bridgeServer.Recorder.VerifyAllInterceptionsEnded(t)
})
}
}
Expand Down Expand Up @@ -213,8 +181,6 @@ func TestAPIDumpPassthrough(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug)

ctx, cancel := context.WithTimeout(t.Context(), time.Second*30)
t.Cleanup(cancel)

Expand All @@ -226,30 +192,16 @@ func TestAPIDumpPassthrough(t *testing.T) {

dumpDir := t.TempDir()

recorderClient := &testutil.MockRecorder{}
prov := tc.providerFunc(upstream.URL, dumpDir)
provs := []aibridge.Provider{prov}
b, err := aibridge.NewRequestBridge(t.Context(), provs, recorderClient, testutil.NewNoopMCPManager(), logger, nil, testTracer)
require.NoError(t, err)

bridgeSrv := httptest.NewUnstartedServer(b)
t.Cleanup(bridgeSrv.Close)
bridgeSrv.Config.BaseContext = func(_ net.Listener) context.Context {
return aibcontext.AsActor(ctx, userID, nil)
}
bridgeSrv.Start()
bridgeServer := newBridgeTestServer(t, ctx, upstream.URL,
withCustomProvider(tc.providerFunc(upstream.URL, dumpDir)),
)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, bridgeSrv.URL+tc.requestPath, nil)
require.NoError(t, err)

resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
bridgeServer.makeRequest(t, http.MethodGet, tc.requestPath, nil)

// Find dump files in the passthrough directory.
passthroughDir := filepath.Join(dumpDir, tc.name, "passthrough")
var reqDumpFile, respDumpFile string
err = filepath.Walk(passthroughDir, func(path string, info os.FileInfo, err error) error {
err := filepath.Walk(passthroughDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
Expand Down
Loading