- Add unit tests for OpenAI edge adapter (chat handler, stream reasoning, tool synthesis, auth routes, test support) - Add roadmap: agent-readable-repository-refactor milestone with subtask plans - Update PHASE.md, priority-queue.md, .gitignore - Delete old edge/node/openai.test files (replaced by proper test structure)
169 lines
5.7 KiB
Go
169 lines
5.7 KiB
Go
package openai
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
edgeservice "iop/apps/edge/internal/service"
|
|
"iop/packages/go/config"
|
|
)
|
|
|
|
func TestRoutesRequireBearerTokenWhenConfigured(t *testing.T) {
|
|
srv := NewServer(config.EdgeOpenAIConf{
|
|
BearerToken: "secret-token",
|
|
Models: []string{"model-a"},
|
|
}, &fakeRunService{}, nil)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
auth string
|
|
wantStatus int
|
|
}{
|
|
{name: "missing", wantStatus: http.StatusUnauthorized},
|
|
{name: "wrong", auth: "Bearer wrong", wantStatus: http.StatusUnauthorized},
|
|
{name: "valid", auth: "Bearer secret-token", wantStatus: http.StatusOK},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
if tc.auth != "" {
|
|
req.Header.Set("Authorization", tc.auth)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
|
|
srv.routes().ServeHTTP(w, req)
|
|
|
|
if w.Code != tc.wantStatus {
|
|
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHealthzDoesNotRequireBearerToken(t *testing.T) {
|
|
srv := NewServer(config.EdgeOpenAIConf{BearerToken: "secret-token"}, &fakeRunService{}, nil)
|
|
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
srv.routes().ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestModelsUsesConfiguredModelsOrTarget(t *testing.T) {
|
|
srv := NewServer(config.EdgeOpenAIConf{Target: "fallback-model"}, &fakeRunService{}, nil)
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
w := httptest.NewRecorder()
|
|
srv.handleModels(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d", w.Code)
|
|
}
|
|
if !strings.Contains(w.Body.String(), "fallback-model") {
|
|
t.Fatalf("expected target model, got %s", w.Body.String())
|
|
}
|
|
|
|
srv = NewServer(config.EdgeOpenAIConf{Models: []string{"a", "b"}}, &fakeRunService{}, nil)
|
|
w = httptest.NewRecorder()
|
|
srv.handleModels(w, req)
|
|
if !strings.Contains(w.Body.String(), `"id":"a"`) || !strings.Contains(w.Body.String(), `"id":"b"`) {
|
|
t.Fatalf("expected configured models, got %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
// TestOpenAIModelsUsesRefreshedCatalog verifies that a provider-pool catalog
|
|
// applied via SetModelCatalog (config refresh apply) is reflected in /v1/models
|
|
// on the next request, replacing the previous catalog snapshot.
|
|
func TestOpenAIModelsUsesRefreshedCatalog(t *testing.T) {
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "model-old"}})
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
w := httptest.NewRecorder()
|
|
srv.handleModels(w, req)
|
|
if body := w.Body.String(); !strings.Contains(body, `"id":"model-old"`) {
|
|
t.Fatalf("expected initial catalog model-old, got %s", body)
|
|
}
|
|
|
|
// Refresh the catalog: the next request must show the new model and not the old.
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "model-new"}})
|
|
w = httptest.NewRecorder()
|
|
srv.handleModels(w, req)
|
|
body := w.Body.String()
|
|
if !strings.Contains(body, `"id":"model-new"`) {
|
|
t.Fatalf("expected refreshed catalog model-new, got %s", body)
|
|
}
|
|
if strings.Contains(body, `"id":"model-old"`) {
|
|
t.Fatalf("stale catalog model-old still present after refresh, got %s", body)
|
|
}
|
|
}
|
|
|
|
// TestOpenAIProviderPoolRouteUsesRefreshedCatalog verifies that provider-pool
|
|
// route resolution reads the refreshed catalog snapshot, so a model added by a
|
|
// config refresh is routed via the provider pool.
|
|
func TestOpenAIProviderPoolRouteUsesRefreshedCatalog(t *testing.T) {
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
if srv.findProviderPoolEntry("model-new") != nil {
|
|
t.Fatal("expected no provider-pool entry before catalog is set")
|
|
}
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{{
|
|
ID: "model-new",
|
|
Providers: map[string]string{"prov-a": "served-a"},
|
|
}})
|
|
dispatch, ok := srv.resolveRouteDispatch("model-new")
|
|
if !ok || !dispatch.ProviderPool {
|
|
t.Fatalf("expected provider-pool dispatch for refreshed model, got ok=%v dispatch=%+v", ok, dispatch)
|
|
}
|
|
}
|
|
|
|
func TestOllamaAPIPassthrough(t *testing.T) {
|
|
fake := &fakeRunService{
|
|
ollamaResp: edgeservice.OllamaAPIView{
|
|
StatusCode: http.StatusAccepted,
|
|
ContentType: "application/json",
|
|
Body: `{"ok":true}`,
|
|
},
|
|
}
|
|
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", TimeoutSec: 15}, fake, nil)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/show", strings.NewReader(`{"model":"gemma4:26b"}`))
|
|
w := httptest.NewRecorder()
|
|
|
|
srv.handleOllamaAPI(w, req)
|
|
|
|
if w.Code != http.StatusAccepted {
|
|
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if fake.ollamaReq.Adapter != "ollama" || fake.ollamaReq.Method != http.MethodPost || fake.ollamaReq.Path != "/api/show" {
|
|
t.Fatalf("passthrough req mismatch: %+v", fake.ollamaReq)
|
|
}
|
|
if fake.ollamaReq.Body != `{"model":"gemma4:26b"}` || fake.ollamaReq.TimeoutSec != 15 {
|
|
t.Fatalf("passthrough body/timeout mismatch: %+v", fake.ollamaReq)
|
|
}
|
|
if w.Body.String() != `{"ok":true}` {
|
|
t.Fatalf("body: got %s", w.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestOllamaAPIPassthroughPreservesConfiguredTarget(t *testing.T) {
|
|
fake := &fakeRunService{
|
|
ollamaResp: edgeservice.OllamaAPIView{
|
|
StatusCode: http.StatusOK,
|
|
ContentType: "application/json",
|
|
Body: `{"models":[]}`,
|
|
},
|
|
}
|
|
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "gemma4:26b", TimeoutSec: 15}, fake, nil)
|
|
req := httptest.NewRequest(http.MethodGet, "/api/tags", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
srv.handleOllamaAPI(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if fake.ollamaReq.Target != "gemma4:26b" {
|
|
t.Fatalf("passthrough target: got %q, want gemma4:26b", fake.ollamaReq.Target)
|
|
}
|
|
}
|