mirror of
https://github.com/Warky-Devs/vecna.git
synced 2026-08-07 10:47:38 +00:00
feat(server): add passthrough proxy for OpenAI-compatible endpoints
* implement proxy handler for various OpenAI API routes * add error handling for request body and response streaming * introduce new error response format for API compatibility * add tests for recover middleware to handle panics gracefully
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/uptrace/bunrouter"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// TestRecoverMiddlewarePanicBeforeResponse verifies a panic in a handler that
|
||||
// hasn't written anything yet is converted into a 500 JSON error instead of
|
||||
// crashing the request goroutine (which would otherwise surface to the client
|
||||
// as a connection reset).
|
||||
func TestRecoverMiddlewarePanicBeforeResponse(t *testing.T) {
|
||||
mw := recoverMiddleware(zap.NewNop())
|
||||
handler := mw(func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||
panic("boom")
|
||||
})
|
||||
|
||||
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/v1/models", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
err := handler(w, bunrouter.Request{Request: req})
|
||||
if err != nil {
|
||||
t.Fatalf("expected recoverMiddleware to swallow the panic and return nil, got %v", err)
|
||||
}
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("expected status 500, got %d", w.Code)
|
||||
}
|
||||
|
||||
var body apiErrorEnvelope
|
||||
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("expected a valid JSON error body, got decode error: %v (body=%q)", err, w.Body.String())
|
||||
}
|
||||
if body.Error.Type != "internal_error" {
|
||||
t.Fatalf("expected error type internal_error, got %q", body.Error.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecoverMiddlewarePanicAfterResponse verifies a panic that happens after
|
||||
// the response has already been committed (e.g. mid-stream) doesn't attempt a
|
||||
// second WriteHeader — it just surfaces as an error for the logger.
|
||||
func TestRecoverMiddlewarePanicAfterResponse(t *testing.T) {
|
||||
mw := recoverMiddleware(zap.NewNop())
|
||||
handler := mw(func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("partial"))
|
||||
panic("boom mid-stream")
|
||||
})
|
||||
|
||||
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/v1/chat/completions", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
err := handler(w, bunrouter.Request{Request: req})
|
||||
if err == nil {
|
||||
t.Fatal("expected an error to be returned once the response was already committed")
|
||||
}
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected the original 200 to stand, got %d", w.Code)
|
||||
}
|
||||
if w.Body.String() != "partial" {
|
||||
t.Fatalf("expected body to be left as-is, got %q", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// TestRecoverMiddlewareNoPanic verifies the happy path is unaffected.
|
||||
func TestRecoverMiddlewareNoPanic(t *testing.T) {
|
||||
mw := recoverMiddleware(zap.NewNop())
|
||||
handler := mw(func(w http.ResponseWriter, req bunrouter.Request) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("ok"))
|
||||
return nil
|
||||
})
|
||||
|
||||
req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/v1/models", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if err := handler(w, bunrouter.Request{Request: req}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if w.Code != http.StatusOK || w.Body.String() != "ok" {
|
||||
t.Fatalf("unexpected response: status=%d body=%q", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user