Files
cpa-plugin/internal/plugin/app_test.go
T

110 lines
3.0 KiB
Go

package plugin
import (
"encoding/base64"
"encoding/json"
"sync"
"testing"
)
func lifecycleRequest(t *testing.T, schema uint32, config string) []byte {
t.Helper()
raw, err := json.Marshal(LifecycleRequest{ConfigYAML: []byte(config), SchemaVersion: schema})
if err != nil {
t.Fatal(err)
}
return raw
}
func TestRegisterNegotiatesSchemaAndDeclaresOnlyUsage(t *testing.T) {
app := NewApp()
raw, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, 99, "enabled: true\ncodex_only: true\n"))
if err != nil {
t.Fatal(err)
}
var env Envelope
if err := json.Unmarshal(raw, &env); err != nil {
t.Fatal(err)
}
if !env.OK {
t.Fatalf("register failed: %s", raw)
}
var got Registration
if err := json.Unmarshal(env.Result, &got); err != nil {
t.Fatal(err)
}
if got.SchemaVersion != SchemaVersion || !got.Capabilities.UsagePlugin {
t.Fatalf("unexpected registration: %+v", got)
}
}
func TestLifecycleConfigYAMLUsesBase64WireEncoding(t *testing.T) {
raw := lifecycleRequest(t, SchemaVersion, "enabled: true\n")
if !json.Valid(raw) {
t.Fatal("invalid JSON")
}
var wire map[string]any
_ = json.Unmarshal(raw, &wire)
want := base64.StdEncoding.EncodeToString([]byte("enabled: true\n"))
if wire["config_yaml"] != want {
t.Fatalf("config_yaml = %v, want %q", wire["config_yaml"], want)
}
}
func TestReconfigureIsAtomicAndFiltersUsage(t *testing.T) {
app := NewApp()
if _, err := app.HandleMethod(MethodPluginRegister, lifecycleRequest(t, SchemaVersion, "enabled: true\ncodex_only: true\n")); err != nil {
t.Fatal(err)
}
for _, record := range []UsageRecord{{Provider: "codex", Model: "gpt-5.5"}, {Provider: "gemini", Model: "gemini-pro"}} {
raw, _ := json.Marshal(record)
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Fatal(err)
}
}
if got := app.Seen(); got != 1 {
t.Fatalf("seen = %d, want 1", got)
}
if _, err := app.HandleMethod(MethodPluginReconfigure, lifecycleRequest(t, SchemaVersion, "enabled: [invalid")); err == nil {
t.Fatal("invalid reconfiguration unexpectedly succeeded")
}
raw, _ := json.Marshal(UsageRecord{Provider: "codex", Model: "gpt-5.5"})
_, _ = app.HandleMethod(MethodUsageHandle, raw)
if got := app.Seen(); got != 2 {
t.Fatalf("last valid config was not retained: seen = %d", got)
}
}
func TestConcurrentUsage(t *testing.T) {
app := NewApp()
raw, _ := json.Marshal(UsageRecord{Provider: "codex", Model: "gpt-5.5"})
var wg sync.WaitGroup
for range 100 {
wg.Add(1)
go func() {
defer wg.Done()
if _, err := app.HandleMethod(MethodUsageHandle, raw); err != nil {
t.Errorf("handle usage: %v", err)
}
}()
}
wg.Wait()
if got := app.Seen(); got != 100 {
t.Fatalf("seen = %d, want 100", got)
}
}
func TestUnknownMethodReturnsErrorEnvelope(t *testing.T) {
raw, err := NewApp().HandleMethod("missing", nil)
if err != nil {
t.Fatal(err)
}
var env Envelope
if err := json.Unmarshal(raw, &env); err != nil {
t.Fatal(err)
}
if env.OK || env.Error == nil || env.Error.Code != "unknown_method" {
t.Fatalf("unexpected envelope: %s", raw)
}
}