Files
coder/aibridge/intercept/apidump/streaming_test.go
T
Paweł Banaszewski e00e85765b chore: move aibridge library code into coder repo (#24190)
This PR merges code from `coder/aibridge` repository into `coder/coder`.
It was split into 4 PRs for easier review but stacked PRs will need to
be merged into this PR so all checks pass.

* https://github.com/coder/coder/pull/24190 -> raw code copy (this PR,
before merging PRs on top of it, it was just 1 commit:
https://github.com/coder/coder/commit/70d33f33200c7e77df910957595715f81f9bec24)
* https://github.com/coder/coder/pull/24570 -> update imports in
`coder/coder` to use copied code
* https://github.com/coder/coder/pull/24586 -> linter fixes and CI
integration (also added README.md)
* https://github.com/coder/coder/pull/24571 -> added exclude to
scripts/check_emdash.sh check

Original PR message (before PR squash):
Moves coder/aibridge code into coder/coder repository.

Omitted files:

- `go.mod`, `go.sum`, `.gitignore`, `.github/workflows/ci.yml,`
`Makefile`, `LICENSE`, `README.md` (modified README.md is added later)
- `.github`, `example`, `buildinfo,` `scripts` directories

Simple verification script (will list omitted files)

```
tmp=$(mktemp -d)
echo "$tmp"
git clone --depth=1 https://github.com/coder/aibridge "$tmp/aibridge"
git clone --depth=1 --branch pb/aibridge-code-move https://github.com/coder/coder "$tmp/coder"
diff -rq --exclude=.git "$tmp/aibridge" "$tmp/coder/aibridge"
# rm -rf "$tmp"
```
2026-04-22 17:01:01 +02:00

130 lines
3.7 KiB
Go

package apidump //nolint:testpackage // shares test helpers with apidump_test.go
import (
"bytes"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"cdr.dev/slog/v3"
"cdr.dev/slog/v3/sloggers/slogtest"
"github.com/coder/quartz"
)
func TestMiddleware_StreamingResponse(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug)
clk := quartz.NewMock(t)
interceptionID := uuid.New()
middleware := NewBridgeMiddleware(tmpDir, "openai", "gpt-4", interceptionID, logger, clk)
require.NotNil(t, middleware)
req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "https://api.openai.com/v1/chat/completions", bytes.NewReader([]byte(`{}`)))
require.NoError(t, err)
// Simulate a streaming response with multiple chunks
chunks := []string{
"data: {\"chunk\": 1}\n\n",
"data: {\"chunk\": 2}\n\n",
"data: {\"chunk\": 3}\n\n",
"data: [DONE]\n\n",
}
// Create a pipe to simulate streaming
pr, pw := io.Pipe()
go func() {
defer pw.Close() //nolint:revive // error handled via pipe read side
for _, chunk := range chunks {
if _, err := pw.Write([]byte(chunk)); err != nil {
return
}
}
}()
resp, err := middleware(req, func(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Proto: "HTTP/1.1",
Header: http.Header{"Content-Type": []string{"text/event-stream"}},
Body: pr,
}, nil
})
require.NoError(t, err)
// Read response in small chunks to simulate streaming consumption
var receivedData bytes.Buffer
buf := make([]byte, 16)
for {
n, err := resp.Body.Read(buf)
if n > 0 {
_, _ = receivedData.Write(buf[:n]) // bytes.Buffer.Write never fails
}
if err == io.EOF {
break
}
require.NoError(t, err)
}
require.NoError(t, resp.Body.Close())
// Verify we received all the data
expectedData := strings.Join(chunks, "")
require.Equal(t, expectedData, receivedData.String())
// Verify the dump file was created and contains all the streamed data
modelDir := filepath.Join(tmpDir, "openai", "gpt-4")
respDumpPath := findDumpFile(t, modelDir, SuffixResponse)
respContent, err := os.ReadFile(respDumpPath)
require.NoError(t, err)
content := string(respContent)
require.Contains(t, content, "HTTP/1.1 200 OK")
require.Contains(t, content, "Content-Type: text/event-stream")
// All chunks should be in the dump
for _, chunk := range chunks {
require.Contains(t, content, chunk)
}
}
func TestMiddleware_PreservesResponseBody(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: false}).Leveled(slog.LevelDebug)
clk := quartz.NewMock(t)
interceptionID := uuid.New()
middleware := NewBridgeMiddleware(tmpDir, "openai", "gpt-4", interceptionID, logger, clk)
require.NotNil(t, middleware)
req, err := http.NewRequestWithContext(t.Context(), http.MethodPost, "https://api.openai.com/v1/chat/completions", bytes.NewReader([]byte(`{}`)))
require.NoError(t, err)
originalRespBody := `{"choices": [{"message": {"content": "hi"}}]}`
resp, err := middleware(req, func(r *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Status: "200 OK",
Proto: "HTTP/1.1",
Header: http.Header{},
Body: io.NopCloser(bytes.NewReader([]byte(originalRespBody))),
}, nil
})
require.NoError(t, err)
defer resp.Body.Close()
// Verify the response body is still readable after middleware
capturedBody, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, originalRespBody, string(capturedBody))
}