153 lines
5.7 KiB
Go
153 lines
5.7 KiB
Go
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)
|
|
}
|
|
}
|