diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-04 14:35:56 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-04 14:35:56 +0300 |
| commit | 48fac4b473e2564e2e82dad36668277f1071ddd0 (patch) | |
| tree | 3e4c75e66a32822d35be6ee947a31a61c54261a2 | |
| parent | d68e5b3b188585fe234d0ce295ec7f054c8bad5f (diff) | |
tests(llm): add OpenAI and Copilot HTTP tests (success + token/error paths); llm coverage ~61%
| -rw-r--r-- | internal/llm/copilot_http_test.go | 50 | ||||
| -rw-r--r-- | internal/llm/openai_http_test.go | 27 |
2 files changed, 77 insertions, 0 deletions
diff --git a/internal/llm/copilot_http_test.go b/internal/llm/copilot_http_test.go new file mode 100644 index 0000000..2a76b46 --- /dev/null +++ b/internal/llm/copilot_http_test.go @@ -0,0 +1,50 @@ +package llm + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +type rtFunc2 func(*http.Request) (*http.Response, error) +func (f rtFunc2) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) } + +func TestCopilot_EnsureSession_AndChat_Success(t *testing.T) { + // Mock chat endpoint + chatSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/chat/completions" { t.Fatalf("unexpected path: %s", r.URL.Path) } + _ = json.NewEncoder(w).Encode(map[string]any{"choices": []map[string]any{{"index":0, "message": map[string]string{"role":"assistant","content":"OK"}}}}) + })) + defer chatSrv.Close() + c := newCopilot(chatSrv.URL, "gpt-4o-mini", "APIKEY", f64p(0.1)).(copilotClient) + // Intercept token endpoint to return a session token + tr := rtFunc2(func(r *http.Request) (*http.Response, error) { + if r.URL.Host == "api.github.com" && r.URL.Path == "/copilot_internal/v2/token" { + rw := httptest.NewRecorder() + _ = json.NewEncoder(rw).Encode(map[string]string{"token":"tok"}) + res := rw.Result() + res.StatusCode = 200 + return res, nil + } + // Fallback to default transport for chatSrv + return http.DefaultTransport.RoundTrip(r) + }) + c.httpClient = &http.Client{Transport: tr, Timeout: 5 * time.Second} + out, err := c.Chat(context.Background(), []Message{{Role:"user", Content:"hi"}}) + if err != nil || out != "OK" { t.Fatalf("copilot chat failed: %v %q", err, out) } +} + +func TestCopilot_HandleNon2xx(t *testing.T) { + b, _ := json.Marshal(map[string]any{"error": map[string]any{"message":"bad","type":"invalid"}}) + resp := &http.Response{StatusCode: 400, Body: io.NopCloser(bytesReader(b))} + if err := handleCopilotNon2xx(resp, time.Now()); err == nil { t.Fatalf("expected error") } +} + +// bytesReader wraps a byte slice with an io.ReadCloser without importing extra. +type bytesReader []byte +func (b bytesReader) Read(p []byte) (int, error) { n := copy(p, b); return n, io.EOF } +func (b bytesReader) Close() error { return nil } diff --git a/internal/llm/openai_http_test.go b/internal/llm/openai_http_test.go new file mode 100644 index 0000000..4989067 --- /dev/null +++ b/internal/llm/openai_http_test.go @@ -0,0 +1,27 @@ +package llm + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +func TestOpenAI_Chat_Success(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/chat/completions" { t.Fatalf("unexpected path: %s", r.URL.Path) } + _ = json.NewEncoder(w).Encode(map[string]any{"choices": []map[string]any{{"index":0, "message": map[string]string{"role":"assistant","content":"OK"}}}}) + })) + defer srv.Close() + c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient) + c.httpClient = srv.Client() + out, err := c.Chat(context.Background(), []Message{{Role:"user", Content:"hi"}}) + if err != nil || out != "OK" { t.Fatalf("openai chat: %v %q", err, out) } +} + +func TestOpenAI_Chat_MissingKey(t *testing.T) { + c := newOpenAI("http://x", "g", "", f64p(0.2)).(openAIClient) + if _, err := c.Chat(context.Background(), []Message{{Role:"user", Content:"hi"}}); err == nil { t.Fatalf("expected error for missing key") } +} + |
