mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
My agent added `//nolint:testpackage` to a test file on one of my PRs. Again. This PR cleans it up across the entire repo and updates the in-repo conventions so future agents stop doing it. The repo already has a precedent for white-box tests that need to touch unexported symbols: `*_internal_test.go` (145+ existing files). The `testpackage` linter's default `skip-regexp` exempts that filename suffix, so the `//nolint:testpackage` directive is unnecessary in every case where someone reached for it. This PR renames 51 such files to `*_internal_test.go` via `git mv` so blame and history follow, and strips the dead directive from 2 files that were already correctly named (`coderd/oauth2provider/authorize_internal_test.go`, `coderd/x/chatd/advisor_internal_test.go`). `.claude/docs/TESTING.md` now documents the rule explicitly under *Test Package Naming*, which is imported into the root `AGENTS.md` via `@.claude/docs/TESTING.md`. The rule: prefer `package foo_test`; if you need internal access, rename the file to `*_internal_test.go` rather than adding a nolint directive.
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package apidump
|
|
|
|
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))
|
|
}
|