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 TestOpenAIRoutesDoNotAcceptAnthropicAPIKey(t *testing.T) { srv := NewServer(config.EdgeOpenAIConf{BearerToken: "secret-token"}, &fakeRunService{}, nil) for _, tc := range []struct { name string method string path string headers map[string]string }{ { name: "models without Anthropic version", method: http.MethodGet, path: "/v1/models", headers: map[string]string{"X-Api-Key": "secret-token"}, }, { name: "chat with unrelated Anthropic version", method: http.MethodPost, path: "/v1/chat/completions", headers: map[string]string{ "X-Api-Key": "secret-token", anthropicVersionHeader: anthropicSupportedVersion, }, }, } { t.Run(tc.name, func(t *testing.T) { req := httptest.NewRequest(tc.method, tc.path, nil) for key, value := range tc.headers { req.Header.Set(key, value) } w := httptest.NewRecorder() srv.routes().ServeHTTP(w, req) if w.Code != http.StatusUnauthorized { 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) } } func TestUnmanagedSingleRequestPresetFailsClosed(t *testing.T) { markedPreset := config.ExecutionPreset{ ID: "preset-marked", Selector: config.ExecutionModelBinding{Model: "plan-model", Options: map[string]any{"reasoning_effort": "high"}}, AllowedModes: []string{config.ModeLight}, Routes: map[string]config.ExecutionRoute{ config.ModeLight: {Stages: []config.ExecutionRouteStage{ {Role: "plan", Model: "plan-model", Options: map[string]any{"reasoning_effort": "high"}}, {Role: "work", Model: "work-model"}, {Role: "review", Model: "review-model", Options: map[string]any{"reasoning_effort": "high"}}, }}, }, SingleRequest: &config.ExecutionSingleRequestPolicy{ WorkspaceRef: "ws-ref", Limits: config.ExecutionSingleRequestLimits{WallClockMS: 30 * 60 * 1000, StageTimeoutMS: 10 * 60 * 1000, MaxToolIterations: 64, MaxOutputBytes: 16 * 1024 * 1024}, Stages: config.ExecutionSingleRequestStages{ Plan: config.ExecutionSingleRequestStageConfig{Model: "plan-model", Options: map[string]any{"reasoning_effort": "high"}}, Work: config.ExecutionSingleRequestStageConfig{Model: "work-model"}, Review: config.ExecutionSingleRequestStageConfig{Model: "review-model", Options: map[string]any{"reasoning_effort": "high"}}, }, }, } // A legacy, unmarked preset (no single_request policy) must keep resolving and // being advertised: fail-closed applies only to marked presets. legacyPreset := config.ExecutionPreset{ ID: "preset-legacy", Selector: config.ExecutionModelBinding{Model: "provider-model-a"}, AllowedModes: []string{config.ModeDirect}, Routes: map[string]config.ExecutionRoute{config.ModeDirect: {}}, } srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil) srv.SetExecutionPresets([]config.ExecutionPreset{markedPreset, legacyPreset}) srv.SetModelCatalog([]config.ModelCatalogEntry{ {ID: "virtual-marked", ExecutionPreset: "preset-marked"}, {ID: "virtual-legacy", ExecutionPreset: "preset-legacy"}, {ID: "plan-model", Providers: map[string]string{"prov-1": "served-plan"}}, {ID: "work-model", Providers: map[string]string{"prov-1": "served-work"}}, {ID: "review-model", Providers: map[string]string{"prov-1": "served-review"}}, {ID: "provider-model-a", Providers: map[string]string{"prov-1": "served-a"}}, }) 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) } body := w.Body.String() if strings.Contains(body, `"id":"virtual-marked"`) { t.Fatalf("marked single-request preset must be omitted from unmanaged /v1/models, got %s", body) } if !strings.Contains(body, `"id":"virtual-legacy"`) { t.Fatalf("unmarked legacy preset must remain listed, got %s", body) } if _, ok := srv.resolveRouteDispatch("virtual-marked"); ok { t.Fatal("expected marked preset to fail closed at resolveRouteDispatch") } disp, ok := srv.resolveRouteDispatch("virtual-legacy") if !ok || !disp.IsPreset || disp.PresetID != "preset-legacy" || disp.ExternalModelID != "virtual-legacy" { t.Fatalf("expected unmarked legacy preset to resolve, got ok=%v disp=%+v", ok, disp) } if disp.SingleRequest != nil { t.Fatalf("legacy preset must not carry a single-request admission: %+v", disp.SingleRequest) } } func TestLegacyVirtualPresetModelResolution(t *testing.T) { preset := config.ExecutionPreset{ ID: "preset-legacy-1", Selector: config.ExecutionModelBinding{ Model: "provider-model-a", }, AllowedModes: []string{config.ModeDirect}, Routes: map[string]config.ExecutionRoute{ config.ModeDirect: {}, }, } srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil) srv.SetExecutionPresets([]config.ExecutionPreset{preset}) srv.SetModelCatalog([]config.ModelCatalogEntry{ { ID: "virtual-legacy", ExecutionPreset: "preset-legacy-1", }, { ID: "provider-model-a", Providers: map[string]string{"prov-1": "served-a"}, }, }) // 1. /v1/models lists virtual-legacy 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(), `"id":"virtual-legacy"`) { t.Fatalf("expected virtual-legacy in /v1/models, got %s", w.Body.String()) } // 2. Dispatch resolution succeeds disp, ok := srv.resolveRouteDispatch("virtual-legacy") if !ok || !disp.IsPreset || disp.PresetID != "preset-legacy-1" || disp.ExternalModelID != "virtual-legacy" { t.Fatalf("resolveRouteDispatch virtual-legacy unexpected: ok=%v disp=%+v", ok, disp) } // 3. When canonical reference "provider-model-a" is missing from catalog, virtual-legacy is filtered out srv.SetModelCatalog([]config.ModelCatalogEntry{ { ID: "virtual-legacy", ExecutionPreset: "preset-legacy-1", }, }) w = httptest.NewRecorder() srv.handleModels(w, req) if strings.Contains(w.Body.String(), `"id":"virtual-legacy"`) { t.Fatalf("expected virtual-legacy to be filtered out when reference is missing, got %s", w.Body.String()) } if _, ok := srv.resolveRouteDispatch("virtual-legacy"); ok { t.Fatalf("expected resolveRouteDispatch to fail when reference is missing") } }