Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
997e5b3
refactor: create internal/integrationtest package infrastructure
pawbana Mar 5, 2026
aae837e
refactor: migrate metrics and trace integration tests to internal/int…
pawbana Mar 5, 2026
99ede95
refactor: migrate responses, circuit_breaker, and apidump integration…
pawbana Mar 5, 2026
be57fa9
Finish bridge_test.go migration: replace all configureFunc/inline boi…
pawbana Mar 5, 2026
35ba107
refactor: convert bridge_test.go from external to internal test package
pawbana Mar 5, 2026
4db485a
refactor: use internal test package, unexport all helpers
pawbana Mar 5, 2026
975e3e3
refactor: split bridge_test.go into per-interceptor test files
pawbana Mar 5, 2026
fb43a44
fixup: apply unexport renames to remaining helper files
pawbana Mar 5, 2026
4315e2f
remove providerFunc
pawbana Mar 5, 2026
ae65a55
merge tests back into bridge_test.go
pawbana Mar 6, 2026
fc1839d
bridge.go -> setupbridge.go + helpers.go
pawbana Mar 6, 2026
bad3ef4
remove withWrappedRecorder
pawbana Mar 6, 2026
3ccddc6
remove actorID arg from setupInjectedToolTest
pawbana Mar 6, 2026
c2b580c
use bridgeTestServer.newRequest
pawbana Mar 6, 2026
462f745
refactor: update makeRequest call sites in trace_test.go for new *htt…
pawbana Mar 6, 2026
8bff746
refactor: update makeRequest call sites in responses_test.go
pawbana Mar 6, 2026
122810e
refactor: update makeRequest call sites in bridge_test.go
pawbana Mar 6, 2026
32de2aa
bridgeServer.makeRequest + tracerSetup + TestSessionIDTracking cleanup
pawbana Mar 6, 2026
6d96370
cleanup
pawbana Mar 6, 2026
24ec8fc
reorder tests in bridge_test.go to original order
pawbana Mar 6, 2026
079884b
remove not needed withLogger
pawbana Mar 6, 2026
7f70b94
fix comments
pawbana Mar 6, 2026
4870b9d
rename
pawbana Mar 6, 2026
0eba5b8
formatting fix
pawbana Mar 6, 2026
3d5bd76
reorder to previous
pawbana Mar 6, 2026
70fea88
recorder cleanup
pawbana Mar 6, 2026
80994d6
MockRecorder Total*Tokens
pawbana Mar 6, 2026
e235208
add Body.Close() to bridgeServer.makeRequest + some test names cleanup
pawbana Mar 6, 2026
fcf0f05
changed TestResponsesParallelToolsOverwritten and TestOpenAIChatCompl…
pawbana Mar 6, 2026
69ed4d7
fmt fix
pawbana Mar 9, 2026
b24c6fc
review 1: fixed setting headers in makeRequest, removed unneded provi…
pawbana Mar 9, 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,111 +13,79 @@ 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)

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

// Find dump files for this interception by walking the dump directory.
var reqDumpFile, respDumpFile string
err = filepath.Walk(dumpDir, func(path string, info os.FileInfo, err error) error {
err := filepath.Walk(dumpDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
Expand Down Expand Up @@ -167,7 +134,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 +180,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 +191,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)
bridgeServer := newBridgeTestServer(t, ctx, upstream.URL,
withCustomProvider(tc.providerFunc(upstream.URL, dumpDir)),
)

bridgeSrv := httptest.NewUnstartedServer(b)
t.Cleanup(bridgeSrv.Close)
bridgeSrv.Config.BaseContext = func(_ net.Listener) context.Context {
return aibcontext.AsActor(ctx, userID, nil)
}
bridgeSrv.Start()

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