939 lines
36 KiB
Go
939 lines
36 KiB
Go
package openai
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"iop/apps/edge/internal/authprojection"
|
|
edgeservice "iop/apps/edge/internal/service"
|
|
"iop/packages/go/config"
|
|
"iop/packages/go/streamgate"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
func makeTestProjection(gen uint64, now time.Time, ttl time.Duration, tokens map[string]string, routes map[string]authprojection.Route) *iop.PrincipalProjection {
|
|
proj := &iop.PrincipalProjection{
|
|
Generation: gen,
|
|
IssuedAtUnixNano: now.UnixNano(),
|
|
ExpiresAtUnixNano: now.Add(ttl).UnixNano(),
|
|
}
|
|
for rawToken, pRef := range tokens {
|
|
digest := sha256.Sum256([]byte(rawToken))
|
|
proj.Tokens = append(proj.Tokens, &iop.ProjectedPrincipalToken{
|
|
TokenDigestSha256: hex.EncodeToString(digest[:]),
|
|
PrincipalRef: pRef,
|
|
PrincipalAlias: pRef + "-alias",
|
|
TokenRef: "tok-" + pRef,
|
|
TokenRevision: gen,
|
|
})
|
|
}
|
|
for _, r := range routes {
|
|
selector := r.ResourceSelector
|
|
if selector == "" {
|
|
selector = "default"
|
|
}
|
|
routeRev := r.RouteRevision
|
|
if routeRev == 0 {
|
|
routeRev = gen
|
|
}
|
|
credRev := r.CredentialRevision
|
|
if credRev == 0 {
|
|
credRev = gen
|
|
}
|
|
proj.Routes = append(proj.Routes, &iop.ProjectedPrincipalRoute{
|
|
RouteId: r.RouteID,
|
|
RouteAlias: r.RouteAlias,
|
|
PrincipalRef: r.PrincipalRef,
|
|
CredentialSlotRef: r.CredentialSlotRef,
|
|
ProfileId: r.ProfileID,
|
|
UpstreamModel: r.UpstreamModel,
|
|
ResourceSelector: selector,
|
|
RouteRevision: routeRev,
|
|
CredentialRevision: credRev,
|
|
})
|
|
}
|
|
return proj
|
|
}
|
|
|
|
func TestManagedModelsListIsPrincipalScoped(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
"token-p2": "principal-2",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {RouteID: "route-p1", PrincipalRef: "principal-1", CredentialSlotRef: "slot-1", ProfileID: "openai", UpstreamModel: "gpt-4o", ResourceSelector: "default"},
|
|
"r2": {RouteID: "route-p2", PrincipalRef: "principal-2", CredentialSlotRef: "slot-2", ProfileID: "openai", UpstreamModel: "gpt-4o", ResourceSelector: "default"},
|
|
})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{
|
|
Models: []string{"global-model"},
|
|
}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
|
|
// Call for P1
|
|
reqP1 := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
reqP1.Header.Set("Authorization", "Bearer token-p1")
|
|
wP1 := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(wP1, reqP1)
|
|
if wP1.Code != http.StatusOK {
|
|
t.Fatalf("P1 status: %d body: %s", wP1.Code, wP1.Body.String())
|
|
}
|
|
var respP1 openAIModelsResponse
|
|
if err := json.Unmarshal(wP1.Body.Bytes(), &respP1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(respP1.Data) != 1 || respP1.Data[0].ID != "route-p1" {
|
|
t.Fatalf("P1 models: expected [route-p1], got %+v", respP1.Data)
|
|
}
|
|
|
|
// Call for P2
|
|
reqP2 := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
reqP2.Header.Set("Authorization", "Bearer token-p2")
|
|
wP2 := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(wP2, reqP2)
|
|
if wP2.Code != http.StatusOK {
|
|
t.Fatalf("P2 status: %d body: %s", wP2.Code, wP2.Body.String())
|
|
}
|
|
var respP2 openAIModelsResponse
|
|
if err := json.Unmarshal(wP2.Body.Bytes(), &respP2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(respP2.Data) != 1 || respP2.Data[0].ID != "route-p2" {
|
|
t.Fatalf("P2 models: expected [route-p2], got %+v", respP2.Data)
|
|
}
|
|
}
|
|
|
|
func TestManagedAnthropicModelsListUsesRouteIDs(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {RouteID: "claude-route-1", PrincipalRef: "principal-1", CredentialSlotRef: "slot-1", ProfileID: "anthropic", UpstreamModel: "claude-3-5-sonnet", ResourceSelector: "default"},
|
|
})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/anthropic/v1/models", nil)
|
|
req.Header.Set("Authorization", "Bearer token-p1")
|
|
req.Header.Set("anthropic-version", "2023-06-01")
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status: %d body: %s", w.Code, w.Body.String())
|
|
}
|
|
var anthropicResp struct {
|
|
Data []struct {
|
|
ID string `json:"id"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &anthropicResp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(anthropicResp.Data) != 1 || anthropicResp.Data[0].ID != "claude-route-1" {
|
|
t.Fatalf("expected [claude-route-1], got %+v", anthropicResp.Data)
|
|
}
|
|
}
|
|
|
|
func TestManagedModelsOmitInactiveRoutes(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj1 := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {RouteID: "route-1", PrincipalRef: "principal-1", CredentialSlotRef: "slot-1", ProfileID: "openai", UpstreamModel: "gpt-4o", ResourceSelector: "default"},
|
|
})
|
|
if err := cache.Apply(proj1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
|
|
// Gen 2 removes route-1
|
|
proj2 := makeTestProjection(2, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, nil)
|
|
if err := cache.Apply(proj2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
req.Header.Set("Authorization", "Bearer token-p1")
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status: %d body: %s", w.Code, w.Body.String())
|
|
}
|
|
var resp openAIModelsResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(resp.Data) != 0 {
|
|
t.Fatalf("expected 0 models for omitted route, got %+v", resp.Data)
|
|
}
|
|
}
|
|
|
|
func TestUnmanagedModelsRetainGlobalCatalog(t *testing.T) {
|
|
srv := NewServer(config.EdgeOpenAIConf{
|
|
Models: []string{"global-model-1", "global-model-2"},
|
|
}, &fakeRunService{}, nil)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status: %d body: %s", w.Code, w.Body.String())
|
|
}
|
|
var resp openAIModelsResponse
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(resp.Data) != 2 {
|
|
t.Fatalf("expected 2 global models, got %d", len(resp.Data))
|
|
}
|
|
}
|
|
|
|
func TestManagedRouteSelectsOnlyBoundSlot(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {
|
|
RouteID: "bound-route",
|
|
PrincipalRef: "principal-1",
|
|
CredentialSlotRef: "slot-bound",
|
|
ProfileID: "profile-bound",
|
|
UpstreamModel: "model-bound",
|
|
ResourceSelector: "default",
|
|
},
|
|
})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "catalog-internal", Providers: map[string]string{"provider-resource": "model-bound"}}})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
|
|
req.Header.Set("Authorization", "Bearer token-p1")
|
|
principal, view, ok := srv.authenticatePrincipal(req)
|
|
if !ok {
|
|
t.Fatal("managed authentication failed")
|
|
}
|
|
ctx := withAuthenticatedProjectionView(withPrincipal(req.Context(), principal), view)
|
|
|
|
disp, err := srv.resolveRouteDispatchForPrincipal(ctx, "bound-route")
|
|
if err != nil {
|
|
t.Fatalf("resolveRouteDispatchForPrincipal failed: %v", err)
|
|
}
|
|
if !disp.Managed || disp.CredentialSlotRef != "slot-bound" || disp.ModelGroupKey != "catalog-internal" {
|
|
t.Fatalf("unexpected dispatch: %+v", disp)
|
|
}
|
|
|
|
pred := disp.CandidatePredicate()
|
|
if pred == nil {
|
|
t.Fatalf("expected non-nil CandidatePredicate")
|
|
}
|
|
|
|
// Correct candidate
|
|
validCandidate := edgeservice.ProviderPoolCandidate{
|
|
ProviderID: "provider-resource",
|
|
ProfileID: "profile-bound",
|
|
ActualModel: "model-bound",
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
}
|
|
if !pred(validCandidate) {
|
|
t.Fatalf("pred rejected valid candidate")
|
|
}
|
|
|
|
// A default selector does not confuse credential slot identity with a
|
|
// provider resource restriction.
|
|
otherResource := validCandidate
|
|
otherResource.ProviderID = "another-provider"
|
|
if !pred(otherResource) {
|
|
t.Fatalf("default selector unexpectedly restricted provider resource")
|
|
}
|
|
|
|
// Mismatched profile
|
|
badProfile := validCandidate
|
|
badProfile.ProfileID = "profile-other"
|
|
if pred(badProfile) {
|
|
t.Fatalf("pred accepted candidate with bad profile")
|
|
}
|
|
|
|
// Mismatched upstream model
|
|
badModel := validCandidate
|
|
badModel.ActualModel = "model-other"
|
|
if pred(badModel) {
|
|
t.Fatalf("pred accepted candidate with bad upstream model")
|
|
}
|
|
}
|
|
|
|
func TestManagedRoutePredicateSurvivesRecovery(t *testing.T) {
|
|
route := authprojection.Route{
|
|
RouteID: "r1",
|
|
CredentialSlotRef: "slot-1",
|
|
ProfileID: "prof-1",
|
|
UpstreamModel: "mod-1",
|
|
}
|
|
pred := managedRouteCandidatePredicate(route, "provider-1")
|
|
streamPred := func(c edgeservice.ProviderPoolCandidate) bool {
|
|
return c.ExecutionPath == "provider_tunnel"
|
|
}
|
|
composed := composeCandidatePredicates(streamPred, pred)
|
|
|
|
candGood := edgeservice.ProviderPoolCandidate{
|
|
ProviderID: "provider-1",
|
|
ProfileID: "prof-1",
|
|
ActualModel: "mod-1",
|
|
ExecutionPath: "provider_tunnel",
|
|
}
|
|
candBadSlot := candGood
|
|
candBadSlot.ProviderID = "provider-2"
|
|
|
|
if !composed(candGood) {
|
|
t.Fatalf("composed predicate rejected valid candidate")
|
|
}
|
|
if composed(candBadSlot) {
|
|
t.Fatalf("composed predicate accepted mismatched slot")
|
|
}
|
|
}
|
|
|
|
func TestManagedCatalogBindingExplicitSelectorAndNoFallback(t *testing.T) {
|
|
route := authprojection.Route{UpstreamModel: "served", ResourceSelector: "provider-resource"}
|
|
catalog := []config.ModelCatalogEntry{{ID: "catalog-internal", Providers: map[string]string{"provider-resource": "served", "other": "served"}}}
|
|
binding, err := resolveManagedCatalogBinding(route, catalog)
|
|
if err != nil || binding.ModelGroupKey != "catalog-internal" || binding.ProviderID != "provider-resource" {
|
|
t.Fatalf("binding=%+v err=%v", binding, err)
|
|
}
|
|
route.ResourceSelector = "default"
|
|
binding, err = resolveManagedCatalogBinding(route, catalog)
|
|
if err != nil || binding.ProviderID != "" {
|
|
t.Fatalf("default binding=%+v err=%v", binding, err)
|
|
}
|
|
route.ResourceSelector = "missing"
|
|
if _, err := resolveManagedCatalogBinding(route, catalog); !errors.Is(err, ErrRouteNotFound) {
|
|
t.Fatalf("missing selector err=%v", err)
|
|
}
|
|
route.ResourceSelector = "default"
|
|
if _, err := resolveManagedCatalogBinding(route, append(catalog, config.ModelCatalogEntry{ID: "another-group", Providers: map[string]string{"provider-resource": "served"}})); !errors.Is(err, ErrRouteNotFound) {
|
|
t.Fatalf("ambiguous catalog err=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestMetadataSpoofIsOverwrittenByManagedBinding(t *testing.T) {
|
|
route := routeDispatch{Managed: true, RouteID: "route-public", CredentialSlotRef: "credential-slot", RouteRevision: 7, CredentialRevision: 11}
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
|
|
req = req.WithContext(withPrincipal(req.Context(), openAIPrincipal{PrincipalRef: "principal", Source: principalSourceProjection}))
|
|
metadata, err := resolveCallerIdentity(req, route, json.RawMessage(`{"iop_route_id":"spoof","iop_route_revision":"0","iop_credential_slot_ref":"spoof","iop_credential_revision":"0"}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if metadata[routeMetaID] != "route-public" || metadata[routeMetaRevision] != "7" || metadata[credentialSlotMeta] != "credential-slot" || metadata[credentialRevisionMeta] != "11" {
|
|
t.Fatalf("trusted metadata=%+v", metadata)
|
|
}
|
|
}
|
|
|
|
func TestManagedProjectionFixturePreservesBindingFacts(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {
|
|
RouteID: "route-public",
|
|
PrincipalRef: "principal-1",
|
|
CredentialSlotRef: "credential-slot",
|
|
ProfileID: "openai",
|
|
UpstreamModel: "gpt-4o",
|
|
ResourceSelector: "provider-resource",
|
|
RouteRevision: 4,
|
|
CredentialRevision: 9,
|
|
},
|
|
})
|
|
if len(proj.Routes) != 1 {
|
|
t.Fatalf("expected 1 route, got %d", len(proj.Routes))
|
|
}
|
|
r := proj.Routes[0]
|
|
if r.ResourceSelector != "provider-resource" {
|
|
t.Errorf("ResourceSelector: got %q, want provider-resource", r.ResourceSelector)
|
|
}
|
|
if r.RouteRevision != 4 {
|
|
t.Errorf("RouteRevision: got %d, want 4", r.RouteRevision)
|
|
}
|
|
if r.CredentialRevision != 9 {
|
|
t.Errorf("CredentialRevision: got %d, want 9", r.CredentialRevision)
|
|
}
|
|
}
|
|
|
|
func TestManagedSurfacesUseDistinctBinding(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
newManaged := func(route authprojection.Route, catalog config.ModelCatalogEntry, candidate edgeservice.ProviderPoolCandidate) (*Server, *providerFakeRunService) {
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{"managed-token": "principal"}, map[string]authprojection.Route{"route": route})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fake := &providerFakeRunService{poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel), poolSelectedCandidate: candidate}
|
|
srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{catalog})
|
|
return srv, fake
|
|
}
|
|
|
|
assertManagedTemplate := func(t *testing.T, group, provider string, meta map[string]string) {
|
|
t.Helper()
|
|
if group != "catalog-internal" {
|
|
t.Fatalf("model group=%q, want catalog-internal", group)
|
|
}
|
|
if provider != "provider-resource" {
|
|
t.Fatalf("provider=%q, want provider-resource", provider)
|
|
}
|
|
if meta[routeMetaID] != "route-public" || meta[routeMetaRevision] != "4" || meta[credentialSlotMeta] != "credential-slot" || meta[credentialRevisionMeta] != "9" {
|
|
t.Fatalf("trusted metadata=%+v", meta)
|
|
}
|
|
}
|
|
|
|
assertManagedBinding := func(t *testing.T, fake *providerFakeRunService) {
|
|
t.Helper()
|
|
run := fake.poolLastRunSnapshot()
|
|
tunnels := fake.tunnelReqsSnapshot()
|
|
if len(tunnels) != 1 {
|
|
t.Fatalf("tunnel requests=%d, want 1", len(tunnels))
|
|
}
|
|
assertManagedTemplate(t, run.ModelGroupKey, run.ProviderID, run.Metadata)
|
|
assertManagedTemplate(t, tunnels[0].ModelGroupKey, tunnels[0].ProviderID, tunnels[0].Metadata)
|
|
}
|
|
|
|
route := authprojection.Route{
|
|
RouteID: "route-public",
|
|
PrincipalRef: "principal",
|
|
CredentialSlotRef: "credential-slot",
|
|
ProfileID: "profile-openai",
|
|
UpstreamModel: "served-model",
|
|
ResourceSelector: "provider-resource",
|
|
RouteRevision: 4,
|
|
CredentialRevision: 9,
|
|
}
|
|
|
|
t.Run("chat", func(t *testing.T) {
|
|
catalog := config.ModelCatalogEntry{ID: "catalog-internal", Providers: map[string]string{"provider-resource": "served-model"}}
|
|
candidate := edgeservice.ProviderPoolCandidate{ProviderID: "provider-resource", ProfileID: "profile-openai", ActualModel: "served-model", ExecutionPath: string(edgeservice.ProviderPoolPathTunnel)}
|
|
srv, fake := newManaged(route, catalog, candidate)
|
|
fake.tunnelFrames = staticProviderTunnelFrames(`{"id":"chatcmpl-1","object":"chat.completion","model":"served-model","choices":[{"message":{"role":"assistant","content":"hi"}}]}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"route-public","metadata":{"iop_route_id":"spoof","iop_route_revision":"0","iop_credential_slot_ref":"spoof","iop_credential_revision":"0"},"messages":[{"role":"user","content":"hi"}]}`))
|
|
req.Header.Set("Authorization", "Bearer managed-token")
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if !strings.Contains(w.Body.String(), `"model":"route-public"`) {
|
|
t.Fatalf("body=%s, want model:route-public", w.Body.String())
|
|
}
|
|
assertManagedBinding(t, fake)
|
|
})
|
|
|
|
t.Run("responses", func(t *testing.T) {
|
|
catalog := config.ModelCatalogEntry{ID: "catalog-internal", Providers: map[string]string{"provider-resource": "served-model"}}
|
|
candidate := edgeservice.ProviderPoolCandidate{ProviderID: "provider-resource", ProfileID: "profile-openai", ActualModel: "served-model", ExecutionPath: string(edgeservice.ProviderPoolPathTunnel)}
|
|
srv, fake := newManaged(route, catalog, candidate)
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(`{"model":"route-public","metadata":{"iop_route_id":"spoof","iop_route_revision":"0","iop_credential_slot_ref":"spoof","iop_credential_revision":"0"},"input":"hi"}`))
|
|
req.Header.Set("Authorization", "Bearer managed-token")
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
assertManagedBinding(t, fake)
|
|
})
|
|
|
|
t.Run("messages", func(t *testing.T) {
|
|
cand := anthropicTestCandidate(t, "anthropic")
|
|
cand.ProviderID = "provider-resource"
|
|
mRoute := authprojection.Route{
|
|
RouteID: "route-public",
|
|
PrincipalRef: "principal",
|
|
CredentialSlotRef: "credential-slot",
|
|
ProfileID: cand.ProfileID,
|
|
UpstreamModel: cand.ActualModel,
|
|
ResourceSelector: "provider-resource",
|
|
RouteRevision: 4,
|
|
CredentialRevision: 9,
|
|
}
|
|
catalog := config.ModelCatalogEntry{ID: "catalog-internal", Providers: map[string]string{"provider-resource": cand.ActualModel}}
|
|
srv, fake := newManaged(mRoute, catalog, cand)
|
|
fake.tunnelFrames = anthropicTunnelFrames(http.StatusOK, "application/json", mustReadAnthropicFixture(t, "native_message.json"))
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(`{"model":"route-public","max_tokens":8,"messages":[{"role":"user","content":"hi"}]}`))
|
|
req.Header.Set("Authorization", "Bearer managed-token")
|
|
req.Header.Set(anthropicVersionHeader, anthropicSupportedVersion)
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
assertManagedBinding(t, fake)
|
|
})
|
|
}
|
|
|
|
func TestManagedCountTokensLocalAndNativeBinding(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
route := authprojection.Route{
|
|
RouteID: "route-public",
|
|
PrincipalRef: "principal",
|
|
CredentialSlotRef: "credential-slot",
|
|
ProfileID: "profile",
|
|
UpstreamModel: "served-model",
|
|
ResourceSelector: "provider-resource",
|
|
RouteRevision: 4,
|
|
CredentialRevision: 9,
|
|
}
|
|
|
|
t.Run("local counter", func(t *testing.T) {
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
if err := cache.Apply(makeTestProjection(1, now, time.Hour, map[string]string{"managed-token": "principal"}, map[string]authprojection.Route{"route": route})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fake := &providerFakeRunService{}
|
|
srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "catalog-internal", Providers: map[string]string{"provider-resource": "served-model"}, TokenCounter: &config.TokenCounterConf{Mode: config.TokenCounterDeterministic}}})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/messages/count_tokens", strings.NewReader(`{"model":"route-public","messages":[{"role":"user","content":"hi"}]}`))
|
|
req.Header.Set("Authorization", "Bearer managed-token")
|
|
req.Header.Set(anthropicVersionHeader, anthropicSupportedVersion)
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if fake.poolSubmitCountSnapshot() != 0 {
|
|
t.Fatalf("expected 0 pool dispatches for local counter, got %d", fake.poolSubmitCountSnapshot())
|
|
}
|
|
})
|
|
|
|
t.Run("native provider-pool fallback", func(t *testing.T) {
|
|
cand := anthropicTestCandidate(t, "anthropic")
|
|
cand.ProviderID = "provider-resource"
|
|
nativeRoute := route
|
|
nativeRoute.ProfileID = cand.ProfileID
|
|
nativeRoute.UpstreamModel = cand.ActualModel
|
|
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
if err := cache.Apply(makeTestProjection(1, now, time.Hour, map[string]string{"managed-token": "principal"}, map[string]authprojection.Route{"route": nativeRoute})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fake := &providerFakeRunService{poolDispatchPath: string(edgeservice.ProviderPoolPathTunnel), poolSelectedCandidate: cand}
|
|
fake.tunnelFrames = anthropicTunnelFrames(http.StatusOK, "application/json", []byte(`{"input_tokens":10}`))
|
|
srv := NewServer(config.EdgeOpenAIConf{}, fake, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "catalog-internal", Providers: map[string]string{"provider-resource": cand.ActualModel}}})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/messages/count_tokens", strings.NewReader(`{"model":"route-public","messages":[{"role":"user","content":"hi"}]}`))
|
|
req.Header.Set("Authorization", "Bearer managed-token")
|
|
req.Header.Set(anthropicVersionHeader, anthropicSupportedVersion)
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", w.Code, w.Body.String())
|
|
}
|
|
if fake.poolSubmitCountSnapshot() != 1 {
|
|
t.Fatalf("expected 1 pool dispatch for native counter, got %d", fake.poolSubmitCountSnapshot())
|
|
}
|
|
if fake.poolLastRun.ModelGroupKey != "catalog-internal" {
|
|
t.Fatalf("model group=%q", fake.poolLastRun.ModelGroupKey)
|
|
}
|
|
meta := fake.poolLastRun.Metadata
|
|
if meta[routeMetaID] != "route-public" || meta[routeMetaRevision] != "4" || meta[credentialSlotMeta] != "credential-slot" || meta[credentialRevisionMeta] != "9" {
|
|
t.Fatalf("trusted metadata=%+v", meta)
|
|
}
|
|
})
|
|
}
|
|
|
|
type managedRecoveryRecorderService struct {
|
|
fakeRunService
|
|
mu sync.Mutex
|
|
submissions []edgeservice.ProviderPoolDispatchRequest
|
|
candidateOrder [][]edgeservice.ProviderPoolCandidate
|
|
}
|
|
|
|
func (s *managedRecoveryRecorderService) SubmitProviderPool(ctx context.Context, req edgeservice.ProviderPoolDispatchRequest) (*edgeservice.ProviderPoolDispatchResult, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
idx := len(s.submissions)
|
|
s.submissions = append(s.submissions, req)
|
|
|
|
var candidates []edgeservice.ProviderPoolCandidate
|
|
if idx < len(s.candidateOrder) {
|
|
candidates = s.candidateOrder[idx]
|
|
}
|
|
|
|
var accepted *edgeservice.ProviderPoolCandidate
|
|
for i := range candidates {
|
|
cand := candidates[i]
|
|
if req.AcceptCandidate != nil {
|
|
if req.AcceptCandidate(cand) {
|
|
if accepted != nil {
|
|
return nil, errors.New("multiple candidates accepted")
|
|
}
|
|
accepted = &cand
|
|
}
|
|
}
|
|
}
|
|
if accepted == nil {
|
|
return nil, edgeservice.ErrProviderPoolCandidateRejected
|
|
}
|
|
|
|
disp := edgeservice.RunDispatch{
|
|
RunID: "run-recovery-" + accepted.ProviderID,
|
|
NodeID: "node-1",
|
|
ModelGroupKey: req.Run.ModelGroupKey,
|
|
Adapter: "tunnel",
|
|
Target: accepted.ActualModel,
|
|
ProviderID: accepted.ProviderID,
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
}
|
|
handle := &fakeTunnelHandle{
|
|
dispatch: disp,
|
|
frames: staticProviderTunnelFrames(`{"ok":true}`),
|
|
}
|
|
return &edgeservice.ProviderPoolDispatchResult{
|
|
Path: edgeservice.ProviderPoolPathTunnel,
|
|
Tunnel: handle,
|
|
DispatchInfo: disp,
|
|
}, nil
|
|
}
|
|
|
|
func TestManagedRecoveryReentersProviderPoolWithFrozenBinding(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {
|
|
RouteID: "bound-route",
|
|
PrincipalRef: "principal-1",
|
|
CredentialSlotRef: "slot-bound",
|
|
ProfileID: "profile-bound",
|
|
UpstreamModel: "model-bound",
|
|
ResourceSelector: "provider-resource",
|
|
RouteRevision: 5,
|
|
CredentialRevision: 12,
|
|
},
|
|
})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
candValid := edgeservice.ProviderPoolCandidate{
|
|
ProviderID: "provider-resource",
|
|
ProfileID: "profile-bound",
|
|
ActualModel: "model-bound",
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
}
|
|
candWrongSlot := edgeservice.ProviderPoolCandidate{
|
|
ProviderID: "wrong-provider",
|
|
ProfileID: "profile-bound",
|
|
ActualModel: "model-bound",
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
}
|
|
candWrongProfile := edgeservice.ProviderPoolCandidate{
|
|
ProviderID: "provider-resource",
|
|
ProfileID: "wrong-profile",
|
|
ActualModel: "model-bound",
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
}
|
|
candWrongModel := edgeservice.ProviderPoolCandidate{
|
|
ProviderID: "provider-resource",
|
|
ProfileID: "profile-bound",
|
|
ActualModel: "wrong-model",
|
|
ExecutionPath: string(edgeservice.ProviderPoolPathTunnel),
|
|
}
|
|
|
|
initialCandidates := []edgeservice.ProviderPoolCandidate{
|
|
candWrongSlot, candWrongProfile, candWrongModel, candValid,
|
|
}
|
|
recoveryCandidates := []edgeservice.ProviderPoolCandidate{
|
|
candWrongModel, candWrongProfile, candWrongSlot, candValid,
|
|
}
|
|
|
|
recorder := &managedRecoveryRecorderService{
|
|
candidateOrder: [][]edgeservice.ProviderPoolCandidate{initialCandidates, recoveryCandidates},
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{}, recorder, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
srv.SetModelCatalog([]config.ModelCatalogEntry{
|
|
{ID: "catalog-internal", Providers: map[string]string{"provider-resource": "model-bound"}},
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{"model":"bound-route","metadata":{"iop_route_id":"spoof"},"messages":[{"role":"user","content":"hi"}]}`))
|
|
req.Header.Set("Authorization", "Bearer token-p1")
|
|
principal, view, ok := srv.authenticatePrincipal(req)
|
|
if !ok {
|
|
t.Fatal("authenticatePrincipal failed")
|
|
}
|
|
ctx := withAuthenticatedProjectionView(withPrincipal(req.Context(), principal), view)
|
|
|
|
route, err := srv.resolveRouteDispatchForPrincipal(ctx, "bound-route")
|
|
if err != nil {
|
|
t.Fatalf("resolveRouteDispatchForPrincipal failed: %v", err)
|
|
}
|
|
|
|
runMeta, err := resolveCallerIdentity(req.WithContext(ctx), route, json.RawMessage(`{"iop_route_id":"spoof"}`))
|
|
if err != nil {
|
|
t.Fatalf("resolveCallerIdentity failed: %v", err)
|
|
}
|
|
requestCtx := openAIRequestContext{
|
|
r: req.WithContext(ctx),
|
|
route: route,
|
|
callerMetadata: runMeta,
|
|
usage: srv.newOpenAIUsageRecorder(ctx, "bound-route", usageEndpointChatCompletions),
|
|
}
|
|
dc := srv.newChatDispatchContext(requestCtx, chatCompletionRequest{Model: "bound-route"}, "prompt", strictOutputPolicy{})
|
|
|
|
initialPoolReq := edgeservice.ProviderPoolDispatchRequest{
|
|
Run: dc.submitReq,
|
|
Tunnel: edgeservice.SubmitProviderTunnelRequest{
|
|
ModelGroupKey: dc.route.effectiveModelGroupKey("bound-route"),
|
|
ProviderID: dc.route.ProviderID,
|
|
Method: http.MethodPost,
|
|
Path: "/v1/chat/completions",
|
|
Operation: string(config.OperationChatCompletions),
|
|
Metadata: dc.runMetadata,
|
|
ProviderPool: true,
|
|
},
|
|
}
|
|
if dc.route.Managed {
|
|
initialPoolReq.AcceptCandidate = dc.route.CandidatePredicate()
|
|
}
|
|
|
|
res1, err := srv.service.SubmitProviderPool(ctx, initialPoolReq)
|
|
if err != nil {
|
|
t.Fatalf("initial SubmitProviderPool failed: %v", err)
|
|
}
|
|
if res1 == nil {
|
|
t.Fatal("initial SubmitProviderPool returned nil result")
|
|
}
|
|
|
|
tunnelReq := openAITunnelStreamGateRequest{
|
|
route: route,
|
|
method: http.MethodPost,
|
|
path: "/v1/chat/completions",
|
|
operation: string(config.OperationChatCompletions),
|
|
modelGroupKey: route.effectiveModelGroupKey("bound-route"),
|
|
metadata: dc.runMetadata,
|
|
pool: &initialPoolReq,
|
|
rewriteBody: func(b []byte, target string) ([]byte, error) {
|
|
return b, nil
|
|
},
|
|
}
|
|
admissionBuilder := newOpenAITunnelRecoveryAdmissionBuilder(tunnelReq)
|
|
recoveryAdmission, err := admissionBuilder(ctx, streamgate.RebuiltRequest{}, []byte(`{"model":"bound-route"}`))
|
|
if err != nil {
|
|
t.Fatalf("newOpenAITunnelRecoveryAdmissionBuilder failed: %v", err)
|
|
}
|
|
if recoveryAdmission.kind != openAIAdmissionPool {
|
|
t.Fatalf("expected recovery admission kind openAIAdmissionPool, got %v", recoveryAdmission.kind)
|
|
}
|
|
|
|
res2, err := srv.service.SubmitProviderPool(ctx, recoveryAdmission.pool)
|
|
if err != nil {
|
|
t.Fatalf("recovery SubmitProviderPool failed: %v", err)
|
|
}
|
|
if res2 == nil {
|
|
t.Fatal("recovery SubmitProviderPool returned nil result")
|
|
}
|
|
|
|
recorder.mu.Lock()
|
|
subs := append([]edgeservice.ProviderPoolDispatchRequest(nil), recorder.submissions...)
|
|
recorder.mu.Unlock()
|
|
|
|
if len(subs) != 2 {
|
|
t.Fatalf("expected 2 submissions to SubmitProviderPool, got %d", len(subs))
|
|
}
|
|
|
|
for i, sub := range subs {
|
|
if sub.Run.ModelGroupKey != "catalog-internal" || sub.Tunnel.ModelGroupKey != "catalog-internal" {
|
|
t.Errorf("submission %d: expected ModelGroupKey 'catalog-internal', got run=%q tunnel=%q", i, sub.Run.ModelGroupKey, sub.Tunnel.ModelGroupKey)
|
|
}
|
|
if sub.Run.Metadata[routeMetaID] != "bound-route" || sub.Run.Metadata[routeMetaRevision] != "5" || sub.Run.Metadata[credentialSlotMeta] != "slot-bound" || sub.Run.Metadata[credentialRevisionMeta] != "12" {
|
|
t.Errorf("submission %d: Run trusted metadata mismatched: %+v", i, sub.Run.Metadata)
|
|
}
|
|
if sub.Tunnel.Metadata[routeMetaID] != "bound-route" || sub.Tunnel.Metadata[routeMetaRevision] != "5" || sub.Tunnel.Metadata[credentialSlotMeta] != "slot-bound" || sub.Tunnel.Metadata[credentialRevisionMeta] != "12" {
|
|
t.Errorf("submission %d: Tunnel trusted metadata mismatched: %+v", i, sub.Tunnel.Metadata)
|
|
}
|
|
if !sub.AcceptCandidate(candValid) {
|
|
t.Errorf("submission %d: rejected valid candidate", i)
|
|
}
|
|
if sub.AcceptCandidate(candWrongSlot) {
|
|
t.Errorf("submission %d: accepted candidate with wrong slot/provider", i)
|
|
}
|
|
if sub.AcceptCandidate(candWrongProfile) {
|
|
t.Errorf("submission %d: accepted candidate with wrong profile", i)
|
|
}
|
|
if sub.AcceptCandidate(candWrongModel) {
|
|
t.Errorf("submission %d: accepted candidate with wrong model", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerationSwapKeepsAuthenticatedRouteView(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
first := makeTestProjection(1, now, time.Hour, map[string]string{"token-p1": "principal-1"}, map[string]authprojection.Route{"r": {RouteID: "route-old", PrincipalRef: "principal-1", CredentialSlotRef: "slot", ProfileID: "profile", UpstreamModel: "served", ResourceSelector: "default"}})
|
|
second := makeTestProjection(2, now, time.Hour, map[string]string{"token-p1": "principal-1"}, map[string]authprojection.Route{"r": {RouteID: "route-new", PrincipalRef: "principal-1", CredentialSlotRef: "slot", ProfileID: "profile", UpstreamModel: "served", ResourceSelector: "default"}})
|
|
if err := cache.Apply(first); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
handler := srv.withAuth(func(w http.ResponseWriter, r *http.Request) {
|
|
if err := cache.Apply(second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
models, err := srv.advertisedModelsForPrincipal(r.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(models) != 1 || models[0].ID != "route-old" {
|
|
t.Fatalf("models after swap: %+v", models)
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
})
|
|
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
|
req.Header.Set("Authorization", "Bearer token-p1")
|
|
w := httptest.NewRecorder()
|
|
handler(w, req)
|
|
if w.Code != http.StatusNoContent {
|
|
t.Fatalf("status=%d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestManagedRouteFailureDoesNotFallback(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {RouteID: "known-route", PrincipalRef: "principal-1", CredentialSlotRef: "slot-1", ProfileID: "prof-1", UpstreamModel: "mod-1", ResourceSelector: "default"},
|
|
})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{
|
|
Target: "legacy-fallback-target",
|
|
ModelRoutes: []config.OpenAIRouteEntry{
|
|
{Model: "unmanaged-model", Target: "unmanaged-target"},
|
|
},
|
|
}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
|
|
req.Header.Set("Authorization", "Bearer token-p1")
|
|
ctx := withPrincipal(req.Context(), openAIPrincipal{PrincipalRef: "principal-1", Source: principalSourceProjection})
|
|
|
|
// Unknown route in managed mode
|
|
_, err := srv.resolveRouteDispatchForPrincipal(ctx, "unmanaged-model")
|
|
if err == nil {
|
|
t.Fatalf("expected error resolving unmanaged-model in managed mode, got nil")
|
|
}
|
|
|
|
// Cross-principal route in managed mode
|
|
_, err = srv.resolveRouteDispatchForPrincipal(ctx, "other-principal-route")
|
|
if err == nil {
|
|
t.Fatalf("expected error resolving cross-principal route, got nil")
|
|
}
|
|
}
|
|
|
|
func TestManagedSurfacesTable(t *testing.T) {
|
|
now := time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC)
|
|
cache := authprojection.NewCache(authprojection.DefaultLimits(), func() time.Time { return now })
|
|
proj := makeTestProjection(1, now, time.Hour, map[string]string{
|
|
"token-p1": "principal-1",
|
|
}, map[string]authprojection.Route{
|
|
"r1": {RouteID: "chat-route", PrincipalRef: "principal-1", CredentialSlotRef: "slot-1", ProfileID: "openai", UpstreamModel: "gpt-4o", ResourceSelector: "default"},
|
|
"r2": {RouteID: "messages-route", PrincipalRef: "principal-1", CredentialSlotRef: "slot-2", ProfileID: "anthropic", UpstreamModel: "claude-3", ResourceSelector: "default"},
|
|
})
|
|
if err := cache.Apply(proj); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
|
|
setManagedPrincipalProjection(srv, cache)
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
headers map[string]string
|
|
body string
|
|
wantStatus int
|
|
}{
|
|
{
|
|
name: "chat completion unknown route",
|
|
method: http.MethodPost, path: "/v1/chat/completions",
|
|
headers: map[string]string{"Authorization": "Bearer token-p1"},
|
|
body: `{"model": "unknown-model", "messages": [{"role": "user", "content": "hi"}]}`,
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "responses unknown route",
|
|
method: http.MethodPost, path: "/v1/responses",
|
|
headers: map[string]string{"Authorization": "Bearer token-p1"},
|
|
body: `{"model": "unknown-model", "input": "hi"}`,
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "anthropic messages unknown route",
|
|
method: http.MethodPost, path: "/v1/messages",
|
|
headers: map[string]string{"Authorization": "Bearer token-p1", "anthropic-version": "2023-06-01"},
|
|
body: `{"model": "unknown-model", "max_tokens": 10, "messages": [{"role": "user", "content": "hi"}]}`,
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "anthropic count_tokens unknown route",
|
|
method: http.MethodPost, path: "/v1/messages/count_tokens",
|
|
headers: map[string]string{"Authorization": "Bearer token-p1", "anthropic-version": "2023-06-01"},
|
|
body: `{"model": "unknown-model", "messages": [{"role": "user", "content": "hi"}]}`,
|
|
wantStatus: http.StatusBadRequest,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
for k, v := range tc.headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
w := httptest.NewRecorder()
|
|
srv.routes().ServeHTTP(w, req)
|
|
if w.Code != tc.wantStatus {
|
|
t.Fatalf("status: got %d, want %d body: %s", w.Code, tc.wantStatus, w.Body.String())
|
|
}
|
|
})
|
|
}
|
|
}
|