package plugin import ( "context" "encoding/json" "net/http" "testing" "time" managedaccess "cpa-ext/internal/access" ) func TestManagedKeyAuthenticationAndLifecycle(t *testing.T) { app := NewApp() if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil { t.Fatal(err) } auth := func(secret string) FrontendAuthResponse { raw, _ := json.Marshal(FrontendAuthRequest{Headers: http.Header{"Authorization": []string{"Bearer " + secret}}}) response, err := app.HandleMethod(MethodFrontendAuthenticate, raw) if err != nil { t.Fatal(err) } var envelope Envelope _ = json.Unmarshal(response, &envelope) var result FrontendAuthResponse _ = json.Unmarshal(envelope.Result, &result) return result } if got := auth("000000"); !got.Authenticated || got.Principal != "key_default" { t.Fatalf("default auth = %+v", got) } xAPIKeyRaw, _ := json.Marshal(FrontendAuthRequest{Headers: http.Header{"X-Api-Key": []string{"000000"}}}) xAPIKeyResponse, _ := app.HandleMethod(MethodFrontendAuthenticate, xAPIKeyRaw) var xAPIKeyEnvelope Envelope _ = json.Unmarshal(xAPIKeyResponse, &xAPIKeyEnvelope) var xAPIKeyResult FrontendAuthResponse _ = json.Unmarshal(xAPIKeyEnvelope.Result, &xAPIKeyResult) if !xAPIKeyResult.Authenticated { t.Fatal("x-api-key did not authenticate") } store, _ := app.currentStore() key, _ := store.ManagedKeyByID(context.Background(), "key_default") key.Status = managedaccess.StatusDisabled if err := store.UpdateManagedKey(context.Background(), key); err != nil { t.Fatal(err) } if got := auth("000000"); got.Authenticated { t.Fatalf("disabled key authenticated: %+v", got) } key.Status = managedaccess.StatusActive if err := store.UpdateManagedKey(context.Background(), key); err != nil { t.Fatal(err) } if got := auth("000000"); !got.Authenticated { t.Fatalf("restored key did not authenticate: %+v", got) } } func TestModelAllowlistAndStrictUpstreamRouting(t *testing.T) { app := NewApp() if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\ncodex_only: false\n"))); err != nil { t.Fatal(err) } store, _ := app.currentStore() if err := store.SyncUpstreamAccounts(context.Background(), []managedaccess.UpstreamAccount{{ CPAAuthID: "oauth-1", CPAAuthIndex: "index-1", Provider: "codex", DisplayName: "OAuth One", LastSeenAt: time.Now(), }}); err != nil { t.Fatal(err) } accounts, _ := store.ListUpstreamAccounts(context.Background()) key, _ := store.ManagedKeyByID(context.Background(), "key_default") key.RouteMode = managedaccess.RouteStrict key.UpstreamAccountID = accounts[0].ID key.AllModels = false key.Models = []string{"gpt-5.6-sol"} if err := store.UpdateManagedKey(context.Background(), key); err != nil { t.Fatal(err) } interceptRaw, _ := json.Marshal(RequestInterceptRequest{ RequestID: "req-1", RequestedModel: "other-model", Metadata: map[string]any{callerScopeMetadata: managedaccess.CallerScope(key.ID)}, }) response, err := app.HandleMethod(MethodRequestBefore, interceptRaw) if err != nil { t.Fatal(err) } var envelope Envelope _ = json.Unmarshal(response, &envelope) var intercept RequestInterceptResponse _ = json.Unmarshal(envelope.Result, &intercept) if !intercept.Terminate || intercept.StatusCode != http.StatusForbidden { t.Fatalf("model denial = %+v", intercept) } pickRaw, _ := json.Marshal(SchedulerPickRequest{ Model: "gpt-5.6-sol", Options: SchedulerOptions{Metadata: map[string]any{callerScopeMetadata: managedaccess.CallerScope(key.ID)}}, Candidates: []SchedulerAuthCandidate{{ID: "oauth-1", Provider: "codex"}}, }) response, err = app.HandleMethod(MethodSchedulerPick, pickRaw) if err != nil { t.Fatal(err) } _ = json.Unmarshal(response, &envelope) var pick SchedulerPickResponse _ = json.Unmarshal(envelope.Result, &pick) if !pick.Handled || pick.AuthID != "oauth-1" { t.Fatalf("scheduler pick = %+v", pick) } afterRaw, _ := json.Marshal(RequestInterceptRequest{ RequestID: "req-1", RequestedModel: "gpt-5.6-sol", Metadata: map[string]any{callerScopeMetadata: managedaccess.CallerScope(key.ID), selectedAuthMetadata: "oauth-1"}, }) afterResponse, _ := app.HandleMethod(MethodRequestAfter, afterRaw) _ = json.Unmarshal(afterResponse, &envelope) _ = json.Unmarshal(envelope.Result, &intercept) if intercept.Terminate { t.Fatalf("selected strict target was rejected: %+v", intercept) } pickRaw, _ = json.Marshal(SchedulerPickRequest{ Model: "gpt-5.6-sol", Options: SchedulerOptions{Metadata: map[string]any{callerScopeMetadata: managedaccess.CallerScope(key.ID)}}, Candidates: []SchedulerAuthCandidate{{ID: "oauth-2", Provider: "codex"}}, }) response, err = app.HandleMethod(MethodSchedulerPick, pickRaw) if err != nil { t.Fatal(err) } _ = json.Unmarshal(response, &envelope) if envelope.OK || envelope.Error == nil || envelope.Error.Code != "bound_upstream_unavailable" { t.Fatalf("missing strict target did not fail closed: %s", response) } } func TestCreateKeyCopiesDefaultRuleSnapshot(t *testing.T) { app := NewApp() if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, testConfig(t, "enabled: true\n"))); err != nil { t.Fatal(err) } response := managementCallBody(t, app, http.MethodPost, managementBase+routeKeys, []byte(`{"name":"alice","secret":"alice-000000"}`)) if response.StatusCode != http.StatusCreated { t.Fatalf("create status=%d body=%s", response.StatusCode, response.Body) } var created managedaccess.ManagedKey if err := json.Unmarshal(response.Body, &created); err != nil { t.Fatal(err) } if created.Secret != "alice-000000" || created.RouteMode != managedaccess.RouteAuto || !created.AllModels { t.Fatalf("created = %+v", created) } }